From 25f12a252d29090dfcaa13734cd2880c3495bf33 Mon Sep 17 00:00:00 2001 From: Aries Date: Wed, 18 Sep 2024 10:16:31 +0800 Subject: [PATCH] Feature/year month day mode in gallery (#6725) * add date mode in gallery * replace localStorage with sfMetadataContext.localStorage, update date tag render * update string to support translation, fix images shift up when date tag was hidden * update gallery ui * fix conflicts, update gallery-group-by-setter * fix conflict --- .../gallery-group-by-setter/index.css | 56 +++++++++++++++++++ .../gallery-group-by-setter/index.js | 51 +++++++++++++++++ .../index.css} | 5 +- .../index.js} | 12 ++-- .../components/data-process-setter/index.js | 6 +- .../metadata/components/view-toolbar/index.js | 9 ++- .../src/metadata/constants/event-bus-type.js | 1 + frontend/src/metadata/constants/index.js | 9 +++ .../src/metadata/utils/cell/column/date.js | 4 ++ .../metadata/views/gallery/gallery-main.js | 28 ++++++++-- frontend/src/metadata/views/gallery/index.css | 16 ++---- frontend/src/metadata/views/gallery/index.js | 42 ++++++++++++-- 12 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.css create mode 100644 frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.js rename frontend/src/metadata/components/data-process-setter/{slider-setter.css => gallery-slider-setter/index.css} (95%) rename frontend/src/metadata/components/data-process-setter/{slider-setter.js => gallery-slider-setter/index.js} (88%) diff --git a/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.css b/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.css new file mode 100644 index 0000000000..c6a8832032 --- /dev/null +++ b/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.css @@ -0,0 +1,56 @@ +.metadata-gallery-group-by-setter { + width: 272px; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid #e2e2e2; + border-radius: 3px; +} + +.metadata-gallery-group-by-setter .metadata-gallery-group-by-button { + width: 66px; + height: 28px; + background-color: #fff; + position: relative; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.875rem; + border: 0; + border-radius: 2px; +} + +.metadata-gallery-group-by-setter .metadata-gallery-group-by-button:hover { + background-color: #f0f0f0; + cursor: pointer; +} + +.metadata-gallery-group-by-setter .metadata-gallery-group-by-button.active { + background-color: #f5f5f5; +} + +.metadata-gallery-group-by-setter .metadata-gallery-group-by-button span { + display: block; + text-align: center; + width: 100%; +} + +.metadata-gallery-group-by-button:not(:first-child)::before { + content: ''; + width: 1px; + height: 22px; + background-color: #e2e2e2; + position: absolute; + top: 50%; + transform: translateY(-50%); + transition: opacity 0.3s; + left: -1px; +} + +.metadata-gallery-group-by-button:hover::before, +.metadata-gallery-group-by-button.active::before, +.metadata-gallery-group-by-button:hover + .metadata-gallery-group-by-button::before, +.metadata-gallery-group-by-button.active + .metadata-gallery-group-by-button::before { + opacity: 0; +} diff --git a/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.js b/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.js new file mode 100644 index 0000000000..a79364d7f4 --- /dev/null +++ b/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.js @@ -0,0 +1,51 @@ +import React, { useState, useCallback, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { EVENT_BUS_TYPE, GALLERY_DATE_MODE } from '../../../constants'; +import { gettext } from '../../../../utils/constants'; + +import './index.css'; + +const DATE_MODE_MAP = { + [GALLERY_DATE_MODE.YEAR]: gettext('Year'), + [GALLERY_DATE_MODE.MONTH]: gettext('Month'), + [GALLERY_DATE_MODE.DAY]: gettext('Day'), + [GALLERY_DATE_MODE.ALL]: gettext('All') +}; + +const GalleryGroupBySetter = ({ view }) => { + const [currentMode, setCurrentMode] = useState(GALLERY_DATE_MODE.DAY); + + useEffect(() => { + const savedValue = window.sfMetadataContext.localStorage.getItem('gallery-group-by', GALLERY_DATE_MODE.DAY); + setCurrentMode(savedValue || GALLERY_DATE_MODE.DAY); + }, [view?._id]); + + const handleGroupByChange = useCallback((newMode) => { + setCurrentMode(newMode); + window.sfMetadataContext.localStorage.setItem('gallery-group-by', newMode); + window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY, newMode); + }, []); + + return ( +
+ {Object.entries(DATE_MODE_MAP).map(([dateMode, label]) => ( + + ))} +
+ ); +}; + +GalleryGroupBySetter.propTypes = { + view: PropTypes.shape({ + _id: PropTypes.string + }) +}; + +export default GalleryGroupBySetter; diff --git a/frontend/src/metadata/components/data-process-setter/slider-setter.css b/frontend/src/metadata/components/data-process-setter/gallery-slider-setter/index.css similarity index 95% rename from frontend/src/metadata/components/data-process-setter/slider-setter.css rename to frontend/src/metadata/components/data-process-setter/gallery-slider-setter/index.css index 7ed97a270e..c2652d720a 100644 --- a/frontend/src/metadata/components/data-process-setter/slider-setter.css +++ b/frontend/src/metadata/components/data-process-setter/gallery-slider-setter/index.css @@ -3,14 +3,15 @@ display: flex; justify-content: space-between; align-items: center; - gap: 4px; + gap: 2px; + margin-left: 8px; text-align: center; } .metadata-slider { -webkit-appearance: none; appearance: none; - width: 100%; + width: 60px; height: 4px; border-radius: 2px; background: #ccc; diff --git a/frontend/src/metadata/components/data-process-setter/slider-setter.js b/frontend/src/metadata/components/data-process-setter/gallery-slider-setter/index.js similarity index 88% rename from frontend/src/metadata/components/data-process-setter/slider-setter.js rename to frontend/src/metadata/components/data-process-setter/gallery-slider-setter/index.js index a91ffa91f4..fa531f5bed 100644 --- a/frontend/src/metadata/components/data-process-setter/slider-setter.js +++ b/frontend/src/metadata/components/data-process-setter/gallery-slider-setter/index.js @@ -1,12 +1,12 @@ import React, { useState, useCallback, useEffect } from 'react'; import PropTypes from 'prop-types'; import { Button, Input } from 'reactstrap'; -import Icon from '../../../components/icon'; -import { EVENT_BUS_TYPE } from '../../constants'; +import Icon from '../../../../components/icon'; +import { EVENT_BUS_TYPE } from '../../../constants'; -import './slider-setter.css'; +import './index.css'; -const SliderSetter = ({ view }) => { +const GallerySliderSetter = ({ view }) => { const [sliderValue, setSliderValue] = useState(() => { const savedValue = window.sfMetadataContext.localStorage.getItem('zoom-gear', 0); return savedValue || 0; @@ -56,8 +56,8 @@ const SliderSetter = ({ view }) => { ); }; -SliderSetter.propTypes = { +GallerySliderSetter.propTypes = { view: PropTypes.object, }; -export default SliderSetter; +export default GallerySliderSetter; diff --git a/frontend/src/metadata/components/data-process-setter/index.js b/frontend/src/metadata/components/data-process-setter/index.js index 0b7c69ff31..92add309eb 100644 --- a/frontend/src/metadata/components/data-process-setter/index.js +++ b/frontend/src/metadata/components/data-process-setter/index.js @@ -1,4 +1,5 @@ -import SliderSetter from './slider-setter'; +import GalleryGroupBySetter from './gallery-group-by-setter/index'; +import GallerySliderSetter from './gallery-slider-setter/index'; import FilterSetter from './filter-setter'; import SortSetter from './sort-setter'; import GroupbySetter from './groupby-setter'; @@ -6,7 +7,8 @@ import PreHideColumnSetter from './pre-hide-column-setter'; import HideColumnSetter from './hide-column-setter'; export { - SliderSetter, + GalleryGroupBySetter, + GallerySliderSetter, FilterSetter, SortSetter, GroupbySetter, diff --git a/frontend/src/metadata/components/view-toolbar/index.js b/frontend/src/metadata/components/view-toolbar/index.js index 380c1cbbaa..7ec417a3f3 100644 --- a/frontend/src/metadata/components/view-toolbar/index.js +++ b/frontend/src/metadata/components/view-toolbar/index.js @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; -import { SliderSetter, FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../data-process-setter'; +import { GalleryGroupBySetter, GallerySliderSetter, FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../data-process-setter'; import { EVENT_BUS_TYPE, VIEW_TYPE } from '../../constants'; import './index.css'; @@ -71,7 +71,12 @@ const ViewToolBar = ({ viewId }) => { onClick={onHeaderClick} >
- {view.type === VIEW_TYPE.GALLERY && } + {viewType === VIEW_TYPE.GALLERY && ( + <> + + + + )} { return dateObj.format('DD.MM.YYYY'); case 'DD.MM.YYYY HH:mm': return dateObj.format('DD.MM.YYYY HH:mm'); + case 'YYYY': + return dateObj.format('YYYY'); + case 'YYYY-MM': + return dateObj.format('YYYY-MM'); default: // Compatible with older versions: if format is null, use defaultFormat return dateObj.format('YYYY-MM-DD'); diff --git a/frontend/src/metadata/views/gallery/gallery-main.js b/frontend/src/metadata/views/gallery/gallery-main.js index a941f9414b..b3d3d84425 100644 --- a/frontend/src/metadata/views/gallery/gallery-main.js +++ b/frontend/src/metadata/views/gallery/gallery-main.js @@ -8,7 +8,7 @@ const GalleryMain = ({ groups, overScan, columns, size, gap }) => { const renderDisplayGroup = useCallback((group) => { const { top: overScanTop, bottom: overScanBottom } = overScan; - const { name, children, height, top } = group; + const { name, children, height, top, paddingTop } = group; // group not in rendering area, return empty div if (top >= overScanBottom || top + height <= overScanTop) { @@ -33,7 +33,7 @@ const GalleryMain = ({ groups, overScan, columns, size, gap }) => { } return ( -
+
{childrenStartIndex === 0 && (
{name}
)}
{ }; GalleryMain.propTypes = { - groups: PropTypes.array, - overScan: PropTypes.object, - columns: PropTypes.number, - size: PropTypes.number, + groups: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string.isRequired, + children: PropTypes.arrayOf(PropTypes.shape({ + top: PropTypes.number.isRequired, + children: PropTypes.arrayOf(PropTypes.shape({ + src: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + })).isRequired, + })).isRequired, + height: PropTypes.number.isRequired, + top: PropTypes.number.isRequired, + paddingTop: PropTypes.number.isRequired, + })), + overScan: PropTypes.shape({ + top: PropTypes.number.isRequired, + bottom: PropTypes.number.isRequired, + }).isRequired, + columns: PropTypes.number.isRequired, + size: PropTypes.number.isRequired, + gap: PropTypes.number.isRequired, }; export default GalleryMain; diff --git a/frontend/src/metadata/views/gallery/index.css b/frontend/src/metadata/views/gallery/index.css index a8a17999c4..d47e2eb49f 100644 --- a/frontend/src/metadata/views/gallery/index.css +++ b/frontend/src/metadata/views/gallery/index.css @@ -1,6 +1,6 @@ .sf-metadata-gallery-container { height: calc(100vh - 100px); - margin: 2px; + padding: 0 16px; position: relative; display: flex; flex-direction: column; @@ -15,20 +15,16 @@ } .metadata-gallery-date-tag { - width: 6rem; - height: 1.5rem; - top: 1rem; - left: 1rem; + height: 2.75rem; position: absolute; + top: 0; + left: 0; display: flex; - justify-content: center; + justify-content: left; align-items: center; - border-radius: 12px; - background-color: #fafaf7; font-size: 0.875rem; - text-align: center; + font-weight: 500; z-index: 1; - opacity: 0.9; user-select: none; } diff --git a/frontend/src/metadata/views/gallery/index.js b/frontend/src/metadata/views/gallery/index.js index 33968948c0..3e696a489d 100644 --- a/frontend/src/metadata/views/gallery/index.js +++ b/frontend/src/metadata/views/gallery/index.js @@ -6,19 +6,21 @@ import { useMetadataView } from '../../hooks/metadata-view'; import { Utils } from '../../../utils/utils'; import { getDateDisplayString } from '../../utils/cell'; import { siteRoot, thumbnailSizeForGrid } from '../../../utils/constants'; -import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, PRIVATE_COLUMN_KEY } from '../../constants'; +import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, PRIVATE_COLUMN_KEY, GALLERY_DATE_MODE, DATE_TAG_HEIGHT } from '../../constants'; import './index.css'; const IMAGE_GAP = 2; const Gallery = () => { - const containerRef = useRef(null); const [isFirstLoading, setFirstLoading] = useState(true); const [isLoadingMore, setLoadingMore] = useState(false); const [zoomGear, setZoomGear] = useState(0); const [containerWidth, setContainerWidth] = useState(0); const [overScan, setOverScan] = useState({ top: 0, bottom: 0 }); + const [mode, setMode] = useState(GALLERY_DATE_MODE.DAY); + + const containerRef = useRef(null); const renderMoreTimer = useRef(null); const { metadata, store } = useMetadataView(); @@ -30,9 +32,22 @@ const Gallery = () => { }, [zoomGear]); const imageSize = useMemo(() => { - return (containerWidth - columns * 2 - 2) / columns; + return (containerWidth - (columns - 1) * 2 - 32) / columns; }, [containerWidth, columns]); + const dateMode = useMemo(() => { + switch (mode) { + case GALLERY_DATE_MODE.YEAR: + return 'YYYY'; + case GALLERY_DATE_MODE.MONTH: + return 'YYYY-MM'; + case GALLERY_DATE_MODE.DAY: + return 'YYYY-MM-DD'; + default: + return 'YYYY-MM-DD'; + } + }, [mode]); + const groups = useMemo(() => { if (isFirstLoading) return []; const firstSort = metadata.view.sorts[0]; @@ -42,7 +57,7 @@ const Gallery = () => { const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME]; const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR]; const path = Utils.encodePath(Utils.joinPath(parentDir, fileName)); - const date = getDateDisplayString(record[firstSort.column_key], 'YYYY-MM-DD'); + const date = mode !== GALLERY_DATE_MODE.ALL ? getDateDisplayString(record[firstSort.column_key], dateMode) : ''; const img = { name: fileName, url: `${siteRoot}lib/${repoID}/file${path}`, @@ -77,17 +92,20 @@ const Gallery = () => { if (!rows[rowIndex]) rows[rowIndex] = { top: top + rowIndex * imageHeight, children: [] }; rows[rowIndex].children.push(child); }); - const height = rows.length * imageHeight; + + const paddingTop = mode === GALLERY_DATE_MODE.ALL ? 0 : DATE_TAG_HEIGHT; + const height = rows.length * imageHeight + paddingTop; _groups.push({ ...__init, top, height, + paddingTop, children: rows }); }); return _groups; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isFirstLoading, metadata, metadata.recordsCount, repoID, columns, imageSize]); + }, [isFirstLoading, metadata, metadata.recordsCount, repoID, columns, imageSize, mode]); const loadMore = useCallback(async () => { if (isLoadingMore) return; @@ -110,6 +128,17 @@ const Gallery = () => { const gear = window.sfMetadataContext.localStorage.getItem('zoom-gear', 0) || 0; setZoomGear(gear); + const mode = window.sfMetadataContext.localStorage.getItem('gallery-group-by', GALLERY_DATE_MODE.DAY) || GALLERY_DATE_MODE.DAY; + setMode(mode); + + const switchGalleryModeSubscribe = window.sfMetadataContext.eventBus.subscribe( + EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY, + (mode) => { + setMode(mode); + window.sfMetadataContext.localStorage.setItem('gallery-group-by', mode); + } + ); + const container = containerRef.current; if (container) { const { offsetWidth, clientHeight } = container; @@ -141,6 +170,7 @@ const Gallery = () => { return () => { container && resizeObserver.unobserve(container); modifyGalleryZoomGearSubscribe(); + switchGalleryModeSubscribe(); renderMoreTimer.current && clearTimeout(renderMoreTimer.current); }; }, []);