diff --git a/.gitignore b/.gitignore index 5285aeb..862fef6 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,5 @@ dist .eslintcache **/*/env-config.js + +**/.DS_Store \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 84c2d21..4d2254c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,8 +15,8 @@ "editor.tabSize": 2, "stylelint.validate": ["css", "scss"], "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.fixAll.stylelint": true + "source.fixAll.eslint": "explicit", + "source.fixAll.stylelint": "explicit" }, } \ No newline at end of file diff --git a/README.md b/README.md index f5e2e64..1a15f94 100644 --- a/README.md +++ b/README.md @@ -82,5 +82,5 @@ docker build -t tourmanique-ui . ### Run ```bash -docker run --detach --publish 7551:80 --rm --name tourmanique-ui --env ENV_KEY="'dev'" --env API_ROOT="'http://localhost:7501/api'" tourmanique-ui +docker run --detach --publish 9551:80 --rm --name tourmanique-ui --env ENV_KEY="'dev'" --env API_ROOT="'http://localhost:7501/api'" tourmanique-ui ``` \ No newline at end of file diff --git a/src/pages/Galleries/components/GalleriesList/GalleriesList.tsx b/src/pages/Galleries/components/GalleriesList/GalleriesList.tsx index 05e743b..8b60311 100644 --- a/src/pages/Galleries/components/GalleriesList/GalleriesList.tsx +++ b/src/pages/Galleries/components/GalleriesList/GalleriesList.tsx @@ -42,6 +42,8 @@ function GalleriesList({ id, name, previewPhotos, + // @ts-ignore + photosCount }) => (
  • { onGalleryDelete(id); }} - photosCount={previewPhotos.length} + photosCount={photosCount} previewPhotos={previewPhotos} />
  • diff --git a/src/pages/Metrics/MetricsPage.scss b/src/pages/Metrics/MetricsPage.scss index d112e40..38b301b 100644 --- a/src/pages/Metrics/MetricsPage.scss +++ b/src/pages/Metrics/MetricsPage.scss @@ -27,5 +27,7 @@ @include noselect; width: 100%; + + border-radius: 3%; } } diff --git a/src/pages/Metrics/MetricsPage.tsx b/src/pages/Metrics/MetricsPage.tsx index b580530..523cf9b 100644 --- a/src/pages/Metrics/MetricsPage.tsx +++ b/src/pages/Metrics/MetricsPage.tsx @@ -1,46 +1,56 @@ /* eslint-disable no-nested-ternary */ -import { useEffect, useState } from 'react'; -import metricImage from '../../assets/images/metric-image.png'; +import { useMemo } from 'react'; import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs'; -import MetricSimilarList from './components/MetricsSimilarList/MetricsSimilarList'; -import { - similarPhotosArray, -} from './MetricsTestValues.data'; -import MetricsInfo from './components/MetricsInfo/MetricsInfo'; +// import MetricSimilarList from './components/MetricsSimilarList/MetricsSimilarList'; + +import { MetricsContainer } from './sections/metrics/MetricsContainer'; +import { useParams } from 'react-router-dom'; +import { MetricsStateContext } from './sections/metrics/state/MetricsStateContext'; +import { MetricsState } from './sections/metrics/state/MetricsState'; +import { PhotoStateContext } from './sections/photo/state/PhotoStateContext'; +import { PhotoContainer } from './sections/photo/PhotoContainer'; +import { PhotoState } from './sections/photo/state/PhotoState'; function MetricsPage() { - const [isLoading, setIsLoading] = useState(true); + const { + galleryId, + id + } = useParams(); - const delay = 1; + const photoState = useMemo( + () => new PhotoState(), + [], + ) - useEffect(() => { - const timer1 = setTimeout(() => setIsLoading(false), delay * 1000); - return () => { - clearTimeout(timer1); - }; - }, []); + const metricsState = useMemo( + () => new MetricsState(), + [], + ) return (
    -
    - metric + -
    + - + + + - + /> */}
    -
    ); } diff --git a/src/pages/Metrics/sections/metrics/MetricsContainer.cy.tsx b/src/pages/Metrics/sections/metrics/MetricsContainer.cy.tsx new file mode 100644 index 0000000..0863505 --- /dev/null +++ b/src/pages/Metrics/sections/metrics/MetricsContainer.cy.tsx @@ -0,0 +1,66 @@ +import { associationsArray, colorsArray, emotionsArray, objectsArray } from "../../MetricsTestValues.data" +import { MetricsContainer } from "./MetricsContainer" +import { MetricsState } from "./state/MetricsState" +import { MetricsStateContext } from "./state/MetricsStateContext" + +const GALLERY_ID = 1 +const PHOTO_ID = 2 + +describe(`ToDosContainer`, () => { + beforeEach(() => { + const metricsResponse = { + metrics: { + uniqueness: { + mainInPercentage: 75, + colorsInPercentage: 44, + otherInPercentage: 32, + }, + features: { + colors: colorsArray, + emotions: emotionsArray, + objects: objectsArray, + associations: associationsArray, + } + }, + } + + cy.intercept( + `GET`, + `*/galleries/${GALLERY_ID}/${PHOTO_ID}/metrics`, + metricsResponse, + ) + }) + + describe(`Initialization`, initializationTests) +}) + + +function initializationTests() { + it(` + GIVEN metrics from network + WHEN render the component + SHOULD see them + `, () => { + mountComponent() + + cy.contains(`75%`) + cy.contains(`44%`) + cy.contains(`32%`) + cy.contains(`calm`) + cy.contains(`sky`) + cy.contains(`vacation`) + }) +} + +function mountComponent() { + const metricsState = new MetricsState() + + cy.mount( + + + , + ) +} \ No newline at end of file diff --git a/src/pages/Metrics/sections/metrics/MetricsContainer.tsx b/src/pages/Metrics/sections/metrics/MetricsContainer.tsx new file mode 100644 index 0000000..deca1e0 --- /dev/null +++ b/src/pages/Metrics/sections/metrics/MetricsContainer.tsx @@ -0,0 +1,45 @@ +import { observer } from "mobx-react-lite" +import { useContext, useEffect } from "react" +import { api } from "../../../../common/utils/HttpClient" +import { MetricsContent } from "./MetricsContent" +import { MetricsStateContext } from "./state/MetricsStateContext" + +export const MetricsContainer = observer(({ + galleryId, + photoId, +}: { + galleryId: number, + photoId: number, +}) => { + const metricsState = useContext(MetricsStateContext) + + useEffect(() => { + async function loadMetricsAsync() { + metricsState.setIsLoading({ + isLoading: true + }) + + try { + const { + data: { + metrics, + } + } = await api.get(`/galleries/${galleryId}/${photoId}/metrics`) + + metricsState.initialize({ + metrics, + }) + } finally { + metricsState.setIsLoading({ + isLoading: false + }) + } + } + + loadMetricsAsync() + }, []) + + return ( + + ) +}) \ No newline at end of file diff --git a/src/pages/Metrics/sections/metrics/MetricsContent.tsx b/src/pages/Metrics/sections/metrics/MetricsContent.tsx new file mode 100644 index 0000000..ff00997 --- /dev/null +++ b/src/pages/Metrics/sections/metrics/MetricsContent.tsx @@ -0,0 +1,16 @@ +import { observer } from "mobx-react-lite" +import { useContext } from "react" +import MetricsInfo from "./components/MetricsInfo/MetricsInfo" +import { MetricsStateContext } from "./state/MetricsStateContext" + +export const MetricsContent = observer(() => { + const metricsState = useContext(MetricsStateContext) + + return ( + + ) +}) \ No newline at end of file diff --git a/src/pages/Metrics/components/MetricsInfo/MetricsInfo.cy.tsx b/src/pages/Metrics/sections/metrics/components/MetricsInfo/MetricsInfo.cy.tsx similarity index 100% rename from src/pages/Metrics/components/MetricsInfo/MetricsInfo.cy.tsx rename to src/pages/Metrics/sections/metrics/components/MetricsInfo/MetricsInfo.cy.tsx diff --git a/src/pages/Metrics/components/MetricsInfo/MetricsInfo.scss b/src/pages/Metrics/sections/metrics/components/MetricsInfo/MetricsInfo.scss similarity index 100% rename from src/pages/Metrics/components/MetricsInfo/MetricsInfo.scss rename to src/pages/Metrics/sections/metrics/components/MetricsInfo/MetricsInfo.scss diff --git a/src/pages/Metrics/components/MetricsInfo/MetricsInfo.tsx b/src/pages/Metrics/sections/metrics/components/MetricsInfo/MetricsInfo.tsx similarity index 86% rename from src/pages/Metrics/components/MetricsInfo/MetricsInfo.tsx rename to src/pages/Metrics/sections/metrics/components/MetricsInfo/MetricsInfo.tsx index d94fb58..6f9cff4 100644 --- a/src/pages/Metrics/components/MetricsInfo/MetricsInfo.tsx +++ b/src/pages/Metrics/sections/metrics/components/MetricsInfo/MetricsInfo.tsx @@ -1,11 +1,34 @@ -import CircleProgressBar from '../../../../components/CircleProgressBar/CircleProgressBar'; -import { - associationsArray, colorsArray, emotionsArray, objectsArray, -} from '../../MetricsTestValues.data'; +import CircleProgressBar from '../../../../../../components/CircleProgressBar/CircleProgressBar'; function MetricsInfo({ + uniqueness: { + mainInPercentage, + colorsInPercentage, + otherInPercentage, + }, + features: { + associations, + colors, + emotions, + objects, + }, isLoading, }: { + uniqueness: { + mainInPercentage: number, + colorsInPercentage: number, + otherInPercentage: number, + }, + features: { + associations: string[], + colors: { + red: number, + green: number, + blue: number, + }[], + emotions: string[], + objects: string[], + } isLoading: boolean; }) { return ( @@ -21,7 +44,7 @@ function MetricsInfo({ ) : ( - {20} + {colorsInPercentage} % completed @@ -52,12 +75,12 @@ function MetricsInfo({

    - {20} + {colorsInPercentage} %

    @@ -80,7 +103,7 @@ function MetricsInfo({ - {15} + {otherInPercentage} % completed @@ -90,12 +113,12 @@ function MetricsInfo({

    - {15} + {otherInPercentage} %

    @@ -122,7 +145,7 @@ function MetricsInfo({ ) : (
    - {colorsArray.map((color) => ( + {colors.map((color) => ( ) : (
    - {emotionsArray.map((emotion) => ( + {emotions.map((emotion) => ( ) : (
    - {objectsArray.map((object) => ( + {objects.map((object) => ( ) : (
    - {associationsArray.map((association) => ( + {associations.map((association) => ( (null as unknown as MetricsState) \ No newline at end of file diff --git a/src/pages/Metrics/sections/photo/PhotoContainer.tsx b/src/pages/Metrics/sections/photo/PhotoContainer.tsx new file mode 100644 index 0000000..b2885bd --- /dev/null +++ b/src/pages/Metrics/sections/photo/PhotoContainer.tsx @@ -0,0 +1,45 @@ +import { observer } from "mobx-react-lite" +import { useContext, useEffect } from "react" +import { api } from "../../../../common/utils/HttpClient" +import { PhotoContent } from "./PhotoContent" +import { PhotoStateContext } from "./state/PhotoStateContext" + +export const PhotoContainer = observer(({ + galleryId, + photoId, +}: { + galleryId: number, + photoId: number, +}) => { + const photoState = useContext(PhotoStateContext) + + useEffect(() => { + async function loadPhotoAsync() { + photoState.setIsLoading({ + isLoading: true + }) + + try { + const { + data: { + photo, + } + } = await api.get(`/galleries/${galleryId}/${photoId}/photo`) + + photoState.initialize({ + photo, + }) + } finally { + photoState.setIsLoading({ + isLoading: false + }) + } + } + + loadPhotoAsync() + }, []) + + return ( + + ) +}) \ No newline at end of file diff --git a/src/pages/Metrics/sections/photo/PhotoContent.tsx b/src/pages/Metrics/sections/photo/PhotoContent.tsx new file mode 100644 index 0000000..712aec5 --- /dev/null +++ b/src/pages/Metrics/sections/photo/PhotoContent.tsx @@ -0,0 +1,30 @@ +import { observer } from "mobx-react-lite" +import { useContext } from "react" +import Skeleton from "react-loading-skeleton" +import { PhotoStateContext } from "./state/PhotoStateContext" + +export const PhotoContent = observer(() => { + const photoState = useContext(PhotoStateContext) + + return ( +
    + { + photoState.isLoading + ? ( + + ) + : ( + metric + ) + } +
    + ) +}) \ No newline at end of file diff --git a/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.cy.tsx b/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.cy.tsx new file mode 100644 index 0000000..810fab8 --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.cy.tsx @@ -0,0 +1,22 @@ +import MetricsInfo from "./MetricsInfo"; + +describe(`MetricsSimilarCard`, () => { + beforeEach(() => { + cy.mount(); + }); + it(`count of colors item SHOULD BE more then one and less then three`, () => { + cy.getByData(`color`).its(`length`).should(`be.within`, 1, 3); + }); + + it(`count of emotions item SHOULD BE more then one`, () => { + cy.getByData(`emotion`).its(`length`).should(`be.at.least`, 1); + }); + + it(`count of objects item SHOULD BE more then one`, () => { + cy.getByData(`object`).its(`length`).should(`be.at.least`, 1); + }); + + it(`count of associations item SHOULD BE more then one`, () => { + cy.getByData(`association`).its(`length`).should(`be.at.least`, 1); + }); +}); diff --git a/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.scss b/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.scss new file mode 100644 index 0000000..84adc49 --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.scss @@ -0,0 +1,305 @@ +.metrics-info { + display: flex; + flex-direction: column; + margin-top: 24px; + width: 100%; + + &__uniqueness-bars { + border-radius: 20px; + padding: 16px; + box-shadow: 1px 4px 16px rgb(180 173 189 / 15%); + + @include tablet { + display: flex; + align-items: center; + padding: 16px 24px; + } + } + + &__stroke-bars { + display: flex; + flex-direction: column; + margin-top: 32px; + width: 100%; + + @include tablet { + margin-top: 0; + margin-left: 24px; + } + + @include desktop { + margin-left: 48px; + } + } + + &__box { + margin-bottom: 24px; + } + + &__bar-box { + display: flex; + align-items: flex-start; + flex-direction: row; + margin-bottom: 16px; + width: 100%; + + &:last-child { + margin-bottom: 0; + } + } + + &__tag { + margin-right: 8px; + margin-bottom: 8px; + border-radius: 8px; + padding: 8px 16px; + line-height: 1.2; + background-color: $color-grey-light-100; + } + + &__tags { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + margin-top: 12px; + } + + &__colors { + display: flex; + align-items: center; + margin-top: 12px; + } + + &__color { + margin-right: 16px; + border-radius: 4px; + width: 24px; + height: 24px; + } + + &__subtitle { + width: 100%; + max-width: 30%; + text-align: left; + color: $color-dark-grey; + } + + &__bar { + display: flex; + align-items: center; + flex-direction: row; + margin-left: 16px; + width: 100%; + max-width: 70%; + } + + &__bar-background { + border-radius: 6px; + width: 100%; + height: 8px; + min-width: 48px; + background: $color-grey-light-300; + } + + &__bar-progress { + border-radius: 6px; + height: 8px; + background: linear-gradient(90deg, #735be4 0%, #9f8af8 100%); + } + + &__bar-percentage-text { + margin-left: 4px; + line-height: 1.1; + + span { + font-size: 12px; + color: $color-dark-grey; + } + } + + &__loader-container { + margin-left: 30px; + width: 100%; + max-width: 70%; + text-align: left; + } + + &__loader-text { + font-size: 12px; + line-height: 1.2; + color: $color-grey-light-400; + } + + &__image-loader-text { + position: relative; + display: inline-block; + font-size: 12px; + line-height: 1.2; + color: $color-grey-light-400; + + &::after { + content: ""; + position: absolute; + right: -3px; + bottom: 4px; + border-radius: 50%; + width: 2px; + height: 2px; + box-sizing: border-box; + background: currentColor; + animation: animloader 1s linear infinite; + } + } + + &__loader { + position: relative; + display: inline-block; + overflow: hidden; + border-radius: 4px; + width: 100%; + + &::after { + content: ""; + position: absolute; + left: 0; + top: 0; + border-radius: 4px; + width: 100%; + height: 8px; + box-sizing: border-box; + animation: defaultLoader 2s linear infinite; + } + + &--purple { + height: 4.8px; + + &::after { + height: 4.8px; + background: linear-gradient(90deg, #6349ea 0%, #bcacff 100%); + } + } + + &--default { + height: 8px; + + &::after { + height: 8px; + background: linear-gradient(90deg, #f9f9fc 0%, #d2d0dc 100%, #d2d0dc 100%); + } + } + } + + &__image-loader-container { + display: flex; + align-items: center; + flex: none; + flex-direction: column; + justify-content: space-between; + margin: 0 auto; + width: 72px; + height: 52px; + + @include tablet { + margin: 0; + } + } + + &__image-loader { + position: relative; + overflow: hidden; + border-radius: 7px; + width: 32px; + height: 32px; + background-color: $color-grey-light-300; + + &::before { + content: ""; + position: absolute; + left: 0; + bottom: 0; + border-radius: 3px; + width: 20px; + height: 20px; + box-shadow: 16px -15px 0 5px $primary-color-default; + background: $primary-color-disable; + transform: rotate(45deg) translate(30%, 40%); + animation: slide 3s infinite ease-in-out alternate; + } + + &::after { + content: ""; + position: absolute; + left: 10px; + top: 7px; + border-radius: 50%; + width: 8px; + height: 8px; + background: $primary-color-hover; + transform: rotate(0deg); + transform-origin: 35px 145px; + animation: sunRotate 3s infinite ease-in-out; + } + } + + &__loader-default-container { + text-align: left; + } + + &__uniqueness { + margin-bottom: 28px; + } + + &__title { + margin-bottom: 16px; + font-size: 18px; + text-align: left; + } + + @include desktop { + margin-top: 0; + } + + @keyframes defaultLoader { + 0% { + left: 0; + transform: translateX(-100%); + } + + 100% { + left: 100%; + transform: translateX(0%); + } + } + + @keyframes animloader { + 0% { + box-shadow: 5px 0 rgb(255 255 255 / 0%), 10px 0 rgb(255 255 255 / 0%); + } + + 50% { + box-shadow: 5px 0 $color-dark-grey, 10px 0 rgb(255 255 255 / 0%); + } + + 100% { + box-shadow: 5px 0 $color-dark-grey, 10px 0 $color-dark-grey; + } + } + + @keyframes slide { + 0% { bottom: -35px; } + 100% { bottom: -35px; } + + 25% { bottom: -2px; } + 75% { bottom: -2px; } + + 20% { bottom: 2px; } + 80% { bottom: 2px; } + } + + @keyframes sunRotate { + 0% { transform: rotate(-15deg); } + 25% { transform: rotate(0deg); } + 75% { transform: rotate(0deg); } + 100% { transform: rotate(25deg); } + } +} diff --git a/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.tsx b/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.tsx new file mode 100644 index 0000000..6f9cff4 --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsInfo/MetricsInfo.tsx @@ -0,0 +1,248 @@ +import CircleProgressBar from '../../../../../../components/CircleProgressBar/CircleProgressBar'; + +function MetricsInfo({ + uniqueness: { + mainInPercentage, + colorsInPercentage, + otherInPercentage, + }, + features: { + associations, + colors, + emotions, + objects, + }, + isLoading, +}: { + uniqueness: { + mainInPercentage: number, + colorsInPercentage: number, + otherInPercentage: number, + }, + features: { + associations: string[], + colors: { + red: number, + green: number, + blue: number, + }[], + emotions: string[], + objects: string[], + } + isLoading: boolean; +}) { + return ( +
    +
    +

    Uniqueness

    +
    + { + isLoading ? ( +
    + + counting +
    + ) : ( + + ) + } + +
    +
    +

    Colors

    + + {isLoading ? ( +
    + + + {colorsInPercentage} + % completed + +
    + ) : ( +
    +
    +
    +
    +

    + {colorsInPercentage} + % +

    +
    + )} + +
    +
    +

    + Objects, + emotions, + associations +

    + + { + isLoading ? ( +
    + + + {otherInPercentage} + % completed + +
    + ) : ( +
    +
    +
    +
    +

    + {otherInPercentage} + % +

    +
    + ) + } + +
    +
    +
    +
    + +
    +

    Features

    +
    +

    Main colors

    + {isLoading ? ( +
    + + + Almost done + +
    + ) : ( +
    + {colors.map((color) => ( + + ))} +
    + )} + +
    +
    +

    Emotions

    + {isLoading ? ( +
    + + + It's going to take a little longer than we expected + +
    + ) : ( +
    + {emotions.map((emotion) => ( + + {emotion} + + ))} +
    + )} + +
    +
    +

    Objects

    + {isLoading ? ( +
    + + + Select objects from the photo (usually takes about 3 minutes) + +
    + ) : ( +
    + {objects.map((object) => ( + + {object} + + ))} +
    + )} + +
    +
    +

    Associations

    + {isLoading ? ( +
    + + + It's going to take a little longer than we expected + +
    + ) : ( +
    + {associations.map((association) => ( + + {association} + + ))} +
    + )} + +
    + +
    + +
    + + ); +} + +export default MetricsInfo; diff --git a/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCard.scss b/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCard.scss new file mode 100644 index 0000000..b80d84a --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCard.scss @@ -0,0 +1,86 @@ +.metric-similar-card { + position: relative; + margin-bottom: 16px; + width: 100%; + + &__image-container { + position: relative; + overflow: hidden; + border-radius: 6px; + min-width: 100%; + aspect-ratio: 8 / 5; + } + + &__subtitle { + margin: 12px 0 4px; + text-align: left; + color: $color-dark-grey; + + @include desktop { + margin: 12px 0 8px; + } + } + + &__tag { + margin-top: 4px; + margin-right: 4px; + border-radius: 8px; + padding: 4px 8px; + line-height: 1.2; + background-color: $color-grey-light-100; + + @include desktop { + margin-top: 8px; + margin-right: 8px; + } + } + + &__tags { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + } + + &__image { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + + &__colors { + position: absolute; + right: 16px; + bottom: 16px; + display: flex; + align-items: center; + } + + &__color { + margin-right: 12px; + border: 2px solid $color-white; + border-radius: 6px; + width: 24px; + height: 24px; + + &:last-child { + margin-right: 0; + } + } + + &:last-child { + margin-bottom: 32px; + } + + @include desktop { + flex: none; + margin-bottom: 24px; + min-width: 224px; + + &:last-child { + margin-bottom: 224px; + } + } +} diff --git a/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCard.tsx b/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCard.tsx new file mode 100644 index 0000000..cc47067 --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCard.tsx @@ -0,0 +1,64 @@ +export type SimilarPhotoType = { + photoId: number; + photoPath: string; + relatedColors: { + red: number; + green: number; + blue: number; + }[]; + relatedFeatures: string[]; +}; + +function MetricsSimilarCard({ + photoId, + photoPath, + relatedColors, + relatedFeatures, +}: SimilarPhotoType) { + return ( +
  • +
    + metric +
    + { + relatedColors.map((color) => ( + + )) + } +
    +
    +
    +

    Related features

    +
    + {relatedFeatures.map((feature) => ( + + {feature} + + ))} +
    + +
    + +
  • + ); +} + +export default MetricsSimilarCard; diff --git a/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCart.cy.tsx b/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCart.cy.tsx new file mode 100644 index 0000000..33354d3 --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsSimilarCard/MetricsSimilarCart.cy.tsx @@ -0,0 +1,81 @@ +import MetricsSimilarCard, { SimilarPhotoType } from "./MetricsSimilarCard"; + +describe(`MetricsSimilarCard`, () => { + it(`count of colors item SHOULD be one`, () => { + mountComponent({ + photoId: 1, + photoPath: `https://stickerbase.ru/wp-content/uploads/2021/07/61607.png`, + relatedColors: [{ + red: 57, + green: 56, + blue: 56, + }, + ], + relatedFeatures: [`calm`], + }); + cy.getByData(`color`).its(`length`).should(`eq`, 1); + }); + + it(`count of colors item SHOULD be two`, () => { + mountComponent({ + photoId: 1, + photoPath: `https://stickerbase.ru/wp-content/uploads/2021/07/61607.png`, + relatedColors: [{ + red: 57, + green: 56, + blue: 56, + }, + { + red: 30, + green: 20, + blue: 16, + }, + ], + relatedFeatures: [`calm`], + }); + cy.getByData(`color`).its(`length`).should(`eq`, 2); + }); + + it(`count of tags item SHOULD be one`, () => { + mountComponent({ + photoId: 1, + photoPath: `https://stickerbase.ru/wp-content/uploads/2021/07/61607.png`, + relatedColors: [{ + red: 57, + green: 56, + blue: 56, + }, + ], + relatedFeatures: [`calm`], + }); + cy.getByData(`tag`).its(`length`).should(`eq`, 1); + }); + + it(`count of tags item SHOULD be two`, () => { + mountComponent({ + photoId: 1, + photoPath: `https://stickerbase.ru/wp-content/uploads/2021/07/61607.png`, + relatedColors: [{ + red: 57, + green: 56, + blue: 56, + }, + ], + relatedFeatures: [`calm`, `love`], + }); + cy.getByData(`tag`).its(`length`).should(`eq`, 2); + }); +}); +function mountComponent({ + photoId, + photoPath, + relatedColors, + relatedFeatures, +}: SimilarPhotoType) { + cy.mount(); +} diff --git a/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.cy.tsx b/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.cy.tsx new file mode 100644 index 0000000..e19d5a2 --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.cy.tsx @@ -0,0 +1,54 @@ +import { SimilarPhotoType } from "../MetricsSimilarCard/MetricsSimilarCard"; +import MetricsSimilarList from "./MetricsSimilarList"; + +describe(`MetricsSimilarList`, () => { + it(`SHOULD render no similar images WHEN there are no images`, () => { + mountComponent({ + isLoading: false, + similarPhotosArray: [], + }); + cy.getByData(`empty-container`).should(`be.exist`); + }); + it(`SHOULD render loading text WHEN loading props true`, () => { + mountComponent({ + isLoading: true, + similarPhotosArray: [], + }); + cy.getByData(`empty-loader-text`).should(`be.exist`); + }); + it(`SHOULD render cards WHEN loading props false and array has values`, () => { + mountComponent({ + isLoading: false, + similarPhotosArray: [{ + photoId: 24, + photoPath: `https://stickerbase.ru/wp-content/uploads/2021/07/61607.png`, + relatedColors: [{ + red: 57, + green: 56, + blue: 56, + }, + { + red: 58, + green: 48, + blue: 106, + }, + ], + relatedFeatures: [`calm`, `mountain`, `tree`, `lake`, `snow`], + }], + }); + cy.getByData(`full-container`).should(`be.exist`); + }); +}); + +function mountComponent({ + isLoading, + similarPhotosArray, +}: { + isLoading: boolean; + similarPhotosArray: SimilarPhotoType[]; +}) { + cy.mount(); +} diff --git a/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.scss b/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.scss new file mode 100644 index 0000000..b016e6c --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.scss @@ -0,0 +1,68 @@ +.metrics-similar-list { + position: relative; + + &__title { + margin-bottom: 16px; + font-size: 18px; + text-align: left; + } + + &__empty { + font-weight: 500; + text-align: center; + color: #7f7d85; + } + + &__empty-container { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + margin-bottom: 32px; + width: 100%; + height: 100%; + min-width: 224px; + + @include desktop { + margin-bottom: 0; + } + } + + &__empty-loader-svg { + margin-bottom: 24px; + } + + &__full-container { + @include reset-list; + + display: flex; + align-items: center; + flex-direction: column; + padding-left: 0; + width: 100%; + height: 100%; + min-width: 224px; + + @include tablet { + max-width: 224px; + } + + @include desktop { + position: absolute; + overflow-y: scroll; + height: 100vh; + + &::-webkit-scrollbar { + display: none; + } + } + } + + @include desktop { + overflow: hidden; + margin-left: 48px; + border-left: 2px solid $color-grey-light-100; + padding-left: 32px; + min-width: 259px; + } +} diff --git a/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.tsx b/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.tsx new file mode 100644 index 0000000..045bfea --- /dev/null +++ b/src/pages/Metrics/sections/photo/components/MetricsSimilarList/MetricsSimilarList.tsx @@ -0,0 +1,65 @@ +/* eslint-disable no-nested-ternary */ +import MetricsSimilarCard, { SimilarPhotoType } from '../MetricsSimilarCard/MetricsSimilarCard'; +import MetricSimilarLoader from '../../../../assets/images/similar-loader-image.svg'; + +function MetricsSimilarList({ + isLoading, + similarPhotosArray, +}: { + isLoading: boolean; + similarPhotosArray: SimilarPhotoType[]; +}) { + return ( +
    +

    Similar photos

    + {isLoading ? ( +
    + loader similar images + + We'll start searching for photos once we've highlighted + all the tags + +
    + ) : similarPhotosArray.length === 0 ? ( +
    + + The photo is unique so it has + no similar photos + +
    + ) : ( +
      + {similarPhotosArray.map((photo) => ( + + ))} +
    + )} +
    + ); +} + +export default MetricsSimilarList; diff --git a/src/pages/Metrics/sections/photo/state/PhotoState.tsx b/src/pages/Metrics/sections/photo/state/PhotoState.tsx new file mode 100644 index 0000000..b954052 --- /dev/null +++ b/src/pages/Metrics/sections/photo/state/PhotoState.tsx @@ -0,0 +1,37 @@ +import { makeAutoObservable } from 'mobx' + +export class PhotoState { + private _photo = { + url: `` + } + private _isLoading: boolean = false + + constructor() { + makeAutoObservable(this) + } + + initialize({ + photo, + }: { + photo: any, + }) { + this._photo = photo + } + + get photo() { + return this._photo + } + + get isLoading() { + return this._isLoading + } + + + setIsLoading({ + isLoading, + }: { + isLoading: boolean, + }) { + this._isLoading = isLoading + } +} \ No newline at end of file diff --git a/src/pages/Metrics/sections/photo/state/PhotoStateContext.tsx b/src/pages/Metrics/sections/photo/state/PhotoStateContext.tsx new file mode 100644 index 0000000..36fdbf0 --- /dev/null +++ b/src/pages/Metrics/sections/photo/state/PhotoStateContext.tsx @@ -0,0 +1,4 @@ +import { createContext } from "react" +import { PhotoState } from "./PhotoState" + +export const PhotoStateContext = createContext(null as unknown as PhotoState) \ No newline at end of file diff --git a/src/styles/index.scss b/src/styles/index.scss index c4972f9..143627f 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -23,8 +23,8 @@ @import "../pages/Photos/components/PhotosList/PhotoList.scss"; @import "../pages/Photos/components/Sort/Sort.scss"; @import "../components/CircleProgressBar/CircleProgressBar.scss"; -@import "../pages/Metrics/components/MetricsSimilarCard/MetricsSimilarCard.scss"; -@import "../pages/Metrics/components/MetricsSimilarList/MetricsSimilarList.scss"; -@import "../pages/Metrics/components/MetricsInfo/MetricsInfo.scss"; +@import "../pages/Metrics/sections/metrics/components/MetricsSimilarCard/MetricsSimilarCard.scss"; +@import "../pages/Metrics/sections/metrics/components/MetricsSimilarList/MetricsSimilarList.scss"; +@import "../pages/Metrics/sections/metrics/components/MetricsInfo/MetricsInfo.scss"; @import "../pages/Galleries/GalleriesPage.scss"; @import "../components/Loader/Loader.scss";