diff --git a/frontend/src/metadata/constants/view/index.js b/frontend/src/metadata/constants/view/index.js index 0be5402b706..263ee47e333 100644 --- a/frontend/src/metadata/constants/view/index.js +++ b/frontend/src/metadata/constants/view/index.js @@ -141,4 +141,24 @@ export const VIEW_DEFAULT_SETTINGS = { } }; +export const VIEW_PROPERTY_KEYS = { + ID: '_id', + TABLE_ID: 'table_id', + NAME: 'name', + BASIC_FILTERS: 'basic_filters', + FILTERS: 'filters', + FILTER_CONJUNCTION: 'filter_conjunction', + SORTS: 'sorts', + GROUPBYS: 'groupbys', + HIDDEN_COLUMNS: 'hidden_columns', + TYPE: 'type', + SETTINGS: 'settings', +}; + +export const VIEW_INCOMPATIBLE_PROPERTIES = [ + VIEW_PROPERTY_KEYS.GROUPBYS, + VIEW_PROPERTY_KEYS.HIDDEN_COLUMNS, + VIEW_PROPERTY_KEYS.SETTINGS, +]; + export const VIEW_TYPES_SUPPORT_SHOW_DETAIL = [VIEW_TYPE.GALLERY, VIEW_TYPE.KANBAN, VIEW_TYPE.FACE_RECOGNITION]; diff --git a/frontend/src/metadata/hooks/metadata.js b/frontend/src/metadata/hooks/metadata.js index e637d0b84c2..a295d3e04f2 100644 --- a/frontend/src/metadata/hooks/metadata.js +++ b/frontend/src/metadata/hooks/metadata.js @@ -397,6 +397,21 @@ export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadata setEnableFaceRecognition(newValue); }, [enableFaceRecognition, currentPath, idViewMap, navigation, addView, deleteView]); + const modifyViewType = useCallback((viewId, update) => { + metadataAPI.modifyView(repoID, viewId, update).then(res => { + setIdViewMap({ + ...idViewMap, + [viewId]: { + ...idViewMap[viewId], + ...update, + }, + }); + }).catch(error => { + const errorMsg = Utils.getErrorMsg(error); + toaster.danger(errorMsg); + }); + }, [repoID, idViewMap]); + useEffect(() => { if (isLoading) return; if (isBeingBuilt) { @@ -461,6 +476,7 @@ export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadata deleteView, updateView, moveView, + modifyViewType, }}> {children} diff --git a/frontend/src/metadata/metadata-tree-view/view.js b/frontend/src/metadata/metadata-tree-view/view.js index 5f6f9005bf5..4360cca1b24 100644 --- a/frontend/src/metadata/metadata-tree-view/view.js +++ b/frontend/src/metadata/metadata-tree-view/view.js @@ -8,10 +8,18 @@ import toaster from '../../components/toast'; import InlineNameEditor from './inline-name-editor'; import { Utils, isMobile } from '../../utils/utils'; import { useMetadata } from '../hooks'; -import { FACE_RECOGNITION_VIEW_ID, METADATA_VIEWS_DRAG_DATA_KEY, METADATA_VIEWS_KEY, VIEW_TYPE_ICON, VIEWS_TYPE_FOLDER, VIEWS_TYPE_VIEW } from '../constants'; +import { FACE_RECOGNITION_VIEW_ID, METADATA_VIEWS_DRAG_DATA_KEY, METADATA_VIEWS_KEY, VIEW_DEFAULT_SETTINGS, VIEW_INCOMPATIBLE_PROPERTIES, VIEW_PROPERTY_KEYS, VIEW_TYPE, VIEW_TYPE_ICON, VIEWS_TYPE_FOLDER, VIEWS_TYPE_VIEW } from '../constants'; import { validateName } from '../utils/validate'; const MOVE_TO_FOLDER_PREFIX = 'move_to_folder_'; +const TURN_VIEW_INTO_PREFIX = 'turn_view_into_'; + +const VIEW_TYPE_LABEL = { + [VIEW_TYPE.GALLERY]: gettext('Gallery'), + [VIEW_TYPE.TABLE]: gettext('Table'), + [VIEW_TYPE.KANBAN]: gettext('Kanban'), + [VIEW_TYPE.MAP]: gettext('Map'), +}; const ViewItem = ({ leftIndent, @@ -28,13 +36,13 @@ const ViewItem = ({ onCopy, onUpdate, }) => { - const { _id: viewId, name: viewName } = view; + const { _id: viewId, name: viewName, type: viewType } = view; const [highlight, setHighlight] = useState(false); const [freeze, setFreeze] = useState(false); const [isSortShow, setSortShow] = useState(false); const [isRenaming, setRenaming] = useState(false); - const { idViewMap, moveView } = useMetadata(); + const { idViewMap, moveView, modifyViewType } = useMetadata(); const otherViewsName = Object.values(idViewMap).filter(v => v._id !== view._id).map(v => v.name); @@ -66,11 +74,24 @@ const ViewItem = ({ subOpList: moveableFolders.map((folder) => ({ key: `${MOVE_TO_FOLDER_PREFIX}${folder._id}`, value: folder.name, icon_dom: })), }); } + const convertableViews = Object.values(VIEW_TYPE).filter(type => type !== viewType && type !== VIEW_TYPE.FACE_RECOGNITION); + value.push({ + key: 'turn', + value: gettext('Turn into'), + subOpListHeader: gettext('Other view'), + subOpList: convertableViews.map((type) => { + return { + key: `${TURN_VIEW_INTO_PREFIX}${type}`, + value: VIEW_TYPE_LABEL[type], + icon_dom: , + }; + }) + }); if (canDelete) { value.push({ key: 'delete', value: gettext('Delete') }); } return value; - }, [folderId, viewId, canUpdate, canDelete, getMoveableFolders]); + }, [folderId, viewId, viewType, canUpdate, canDelete, getMoveableFolders]); const onMouseEnter = useCallback(() => { if (freeze) return; @@ -117,7 +138,24 @@ const ViewItem = ({ onDelete(viewId, isSelected); return; } - }, [folderId, viewId, isSelected, onDelete, onCopy, moveView]); + + if (operationKey.startsWith(TURN_VIEW_INTO_PREFIX)) { + const targetType = operationKey.split(TURN_VIEW_INTO_PREFIX)[1]; + const update = { type: targetType }; + VIEW_INCOMPATIBLE_PROPERTIES.forEach(key => { + if (key === VIEW_PROPERTY_KEYS.SETTINGS) { + update[key] = VIEW_DEFAULT_SETTINGS[targetType]; + return; + } + update[key] = []; + }); + modifyViewType(viewId, update); + if (isSelected && window.sfMetadataStore) { + window.sfMetadataStore.modifyViewType(viewId, update); + } + return; + } + }, [folderId, viewId, isSelected, onDelete, onCopy, moveView, modifyViewType]); const renameView = useCallback((name, failCallback) => { onUpdate(viewId, { name }, () => { diff --git a/frontend/src/metadata/store/index.js b/frontend/src/metadata/store/index.js index dd74cc0332d..38f00343cfa 100644 --- a/frontend/src/metadata/store/index.js +++ b/frontend/src/metadata/store/index.js @@ -518,6 +518,14 @@ class Store { this.applyOperation(operation); }; + modifyViewType(viewId, update) { + const type = OPERATION_TYPE.MODIFY_VIEW_TYPE; + const operation = this.createOperation({ + type, repo_id: this.repoId, view_id: viewId, update + }); + this.applyOperation(operation); + } + // column insertColumn = (name, columnType, { key, data }) => { const operationType = OPERATION_TYPE.INSERT_COLUMN; diff --git a/frontend/src/metadata/store/operations/apply.js b/frontend/src/metadata/store/operations/apply.js index c14935046d7..aa7f6ad66cd 100644 --- a/frontend/src/metadata/store/operations/apply.js +++ b/frontend/src/metadata/store/operations/apply.js @@ -172,6 +172,11 @@ export default function apply(data, operation) { data.view = { ...data.view, ...update }; return data; } + case OPERATION_TYPE.MODIFY_VIEW_TYPE: { + const { update } = operation; + data.view = { ...data.view, ...update }; + return data; + } case OPERATION_TYPE.INSERT_COLUMN: { const { column } = operation; const newColumn = new Column(column); diff --git a/frontend/src/metadata/store/operations/constants.js b/frontend/src/metadata/store/operations/constants.js index f1199a6b761..85a0c8c69b4 100644 --- a/frontend/src/metadata/store/operations/constants.js +++ b/frontend/src/metadata/store/operations/constants.js @@ -6,6 +6,7 @@ export const OPERATION_TYPE = { MODIFY_HIDDEN_COLUMNS: 'modify_hidden_columns', MODIFY_SETTINGS: 'modify_settings', MODIFY_LOCAL_VIEW: 'modify_local_view', + MODIFY_VIEW_TYPE: 'modify_view_type', // column INSERT_COLUMN: 'insert_column', @@ -60,6 +61,7 @@ export const OPERATION_ATTRIBUTES = { [OPERATION_TYPE.MODIFY_GROUPBYS]: ['repo_id', 'view_id', 'groupbys'], [OPERATION_TYPE.MODIFY_HIDDEN_COLUMNS]: ['repo_id', 'view_id', 'hidden_columns'], [OPERATION_TYPE.MODIFY_LOCAL_VIEW]: ['repo_id', 'view_id', 'update'], + [OPERATION_TYPE.MODIFY_VIEW_TYPE]: ['repo_id', 'view_id', 'update'], [OPERATION_TYPE.INSERT_COLUMN]: ['repo_id', 'name', 'column_type', 'column_key', 'data', 'column'], [OPERATION_TYPE.RENAME_COLUMN]: ['repo_id', 'column_key', 'new_name', 'old_name'], @@ -114,6 +116,7 @@ export const VIEW_OPERATION = [ OPERATION_TYPE.MODIFY_SORTS, OPERATION_TYPE.MODIFY_GROUPBYS, OPERATION_TYPE.MODIFY_HIDDEN_COLUMNS, + OPERATION_TYPE.MODIFY_VIEW_TYPE, ]; export const COLUMN_OPERATION = [ diff --git a/frontend/src/metadata/store/server-operator.js b/frontend/src/metadata/store/server-operator.js index 3004aa07753..babd9e612ac 100644 --- a/frontend/src/metadata/store/server-operator.js +++ b/frontend/src/metadata/store/server-operator.js @@ -205,6 +205,10 @@ class ServerOperator { }); break; } + case OPERATION_TYPE.MODIFY_VIEW_TYPE: { + callback({ operation }); + break; + } // face table op case OPERATION_TYPE.RENAME_PEOPLE_NAME: { diff --git a/frontend/src/metadata/views/gallery/main.js b/frontend/src/metadata/views/gallery/main.js index 8b0e6b1cbd1..1a780e2b508 100644 --- a/frontend/src/metadata/views/gallery/main.js +++ b/frontend/src/metadata/views/gallery/main.js @@ -8,7 +8,7 @@ import { useMetadataView } from '../../hooks/metadata-view'; import { Utils } from '../../../utils/utils'; import { getDateDisplayString, getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell'; import { siteRoot, fileServerRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal, thumbnailDefaultSize } from '../../../utils/constants'; -import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, STORAGE_GALLERY_DATE_MODE_KEY, STORAGE_GALLERY_ZOOM_GEAR_KEY } from '../../constants'; +import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, STORAGE_GALLERY_DATE_MODE_KEY, STORAGE_GALLERY_ZOOM_GEAR_KEY, VIEW_TYPE_DEFAULT_SORTS, VIEW_TYPE } from '../../constants'; import { getRowById } from '../../utils/table'; import { getEventClassName } from '../../utils/common'; import GalleryContextmenu from './context-menu'; @@ -38,7 +38,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord, const images = useMemo(() => { if (isFirstLoading) return []; if (!Array.isArray(metadata.rows) || metadata.rows.length === 0) return []; - const firstSort = metadata.view.sorts[0]; + const firstSort = metadata.view.sorts[0] || VIEW_TYPE_DEFAULT_SORTS[VIEW_TYPE.GALLERY]; return metadata.rows .filter(record => Utils.imageCheck(getFileNameFromRecord(record))) .map(record => {