1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-24 21:07:17 +00:00

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
This commit is contained in:
Aries
2024-09-18 10:16:31 +08:00
committed by GitHub
parent ce39cbdc8c
commit 25f12a252d
12 changed files with 205 additions and 34 deletions

View File

@@ -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;
}

View File

@@ -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 (
<div className="metadata-gallery-group-by-setter">
{Object.entries(DATE_MODE_MAP).map(([dateMode, label]) => (
<button
key={dateMode}
className={classnames('metadata-gallery-group-by-button', { active: currentMode === dateMode })}
onClick={() => handleGroupByChange(dateMode)}
>
<span>{label}</span>
</button>
))}
</div>
);
};
GalleryGroupBySetter.propTypes = {
view: PropTypes.shape({
_id: PropTypes.string
})
};
export default GalleryGroupBySetter;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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}
>
<div className="sf-metadata-tool-left-operations">
{view.type === VIEW_TYPE.GALLERY && <SliderSetter view={view} />}
{viewType === VIEW_TYPE.GALLERY && (
<>
<GalleryGroupBySetter view={view} />
<GallerySliderSetter view={view} />
</>
)}
<FilterSetter
isNeedSubmit={true}
wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-filter"

View File

@@ -52,4 +52,5 @@ export const EVENT_BUS_TYPE = {
// gallery
MODIFY_GALLERY_ZOOM_GEAR: 'modify_gallery_zoom_gear',
SWITCH_GALLERY_GROUP_BY: 'switch_gallery_group_by',
};

View File

@@ -118,3 +118,12 @@ export {
TRANSFER_TYPES,
metadataZIndexes,
};
export const DATE_TAG_HEIGHT = 44;
export const GALLERY_DATE_MODE = {
YEAR: 'year',
MONTH: 'month',
DAY: 'day',
ALL: 'all',
};

View File

@@ -42,6 +42,10 @@ const getDateDisplayString = (date, format) => {
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');

View File

@@ -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 (
<div key={name} className="metadata-gallery-date-group w-100" style={{ height }}>
<div key={name} className="metadata-gallery-date-group w-100" style={{ height, paddingTop }}>
{childrenStartIndex === 0 && (<div className="metadata-gallery-date-tag">{name}</div>)}
<div
className="metadata-gallery-image-list"
@@ -67,10 +67,26 @@ const GalleryMain = ({ groups, overScan, columns, size, gap }) => {
};
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;

View File

@@ -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;
}

View File

@@ -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);
};
}, []);