mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-25 06:33:48 +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:
@@ -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;
|
||||
}
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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,
|
||||
|
@@ -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"
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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');
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
};
|
||||
}, []);
|
||||
|
Reference in New Issue
Block a user