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 => {