2024-08-12 17:15:56 +08:00
|
|
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
import metadataAPI from '../api';
|
|
|
|
import { Utils } from '../../utils/utils';
|
|
|
|
import toaster from '../../components/toast';
|
|
|
|
import { gettext } from '../../utils/constants';
|
|
|
|
import { PRIVATE_FILE_TYPE } from '../../constants';
|
|
|
|
|
|
|
|
// This hook provides content related to seahub interaction, such as whether to enable extended attributes, views data, etc.
|
|
|
|
const MetadataContext = React.createContext(null);
|
|
|
|
|
2024-09-05 13:42:07 +08:00
|
|
|
export const MetadataProvider = ({ repoID, hideMetadataView, selectMetadataView, children }) => {
|
2024-08-12 17:15:56 +08:00
|
|
|
const enableMetadataManagement = useMemo(() => {
|
|
|
|
return window.app.pageOptions.enableMetadataManagement;
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
}, [window.app.pageOptions.enableMetadataManagement]);
|
|
|
|
|
|
|
|
const [enableMetadata, setEnableExtendedProperties] = useState(false);
|
|
|
|
const [showFirstView, setShowFirstView] = useState(false);
|
|
|
|
const [navigation, setNavigation] = useState([]);
|
2024-09-05 13:42:07 +08:00
|
|
|
const [, setCount] = useState(0);
|
2024-08-12 17:15:56 +08:00
|
|
|
const viewsMap = useRef({});
|
|
|
|
|
2024-08-15 17:38:42 +08:00
|
|
|
const cancelURLView = useCallback(() => {
|
|
|
|
// If attribute extension is turned off, unmark the URL
|
|
|
|
const { origin, pathname, search } = window.location;
|
|
|
|
const urlParams = new URLSearchParams(search);
|
|
|
|
const viewID = urlParams.get('view');
|
|
|
|
if (viewID) {
|
|
|
|
const url = `${origin}${pathname}`;
|
|
|
|
window.history.pushState({ url: url, path: '' }, '', url);
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
2024-08-12 17:15:56 +08:00
|
|
|
useEffect(() => {
|
|
|
|
if (!enableMetadataManagement) {
|
2024-08-15 17:38:42 +08:00
|
|
|
cancelURLView();
|
2024-08-12 17:15:56 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
metadataAPI.getMetadataStatus(repoID).then(res => {
|
2024-08-15 17:38:42 +08:00
|
|
|
const enableMetadata = res.data.enabled;
|
|
|
|
if (!enableMetadata) {
|
|
|
|
cancelURLView();
|
|
|
|
}
|
|
|
|
setEnableExtendedProperties(enableMetadata);
|
2024-08-12 17:15:56 +08:00
|
|
|
}).catch(error => {
|
|
|
|
const errorMsg = Utils.getErrorMsg(error, true);
|
|
|
|
toaster.danger(errorMsg);
|
|
|
|
setEnableExtendedProperties(false);
|
|
|
|
});
|
2024-08-15 17:38:42 +08:00
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2024-08-12 17:15:56 +08:00
|
|
|
}, [repoID, enableMetadataManagement]);
|
|
|
|
|
|
|
|
const updateEnableMetadata = useCallback((newValue) => {
|
|
|
|
if (newValue === enableMetadata) return;
|
|
|
|
if (!newValue) {
|
|
|
|
hideMetadataView && hideMetadataView();
|
2024-08-15 17:38:42 +08:00
|
|
|
cancelURLView();
|
2024-08-12 17:15:56 +08:00
|
|
|
} else {
|
|
|
|
setShowFirstView(true);
|
|
|
|
}
|
|
|
|
setEnableExtendedProperties(newValue);
|
2024-08-15 17:38:42 +08:00
|
|
|
}, [enableMetadata, hideMetadataView, cancelURLView]);
|
2024-08-12 17:15:56 +08:00
|
|
|
|
|
|
|
// views
|
|
|
|
useEffect(() => {
|
|
|
|
if (enableMetadata) {
|
|
|
|
metadataAPI.listViews(repoID).then(res => {
|
|
|
|
const { navigation, views } = res.data;
|
|
|
|
if (Array.isArray(views)) {
|
|
|
|
views.forEach(view => {
|
|
|
|
viewsMap.current[view._id] = view;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
setNavigation(navigation);
|
|
|
|
}).catch(error => {
|
|
|
|
const errorMsg = Utils.getErrorMsg(error);
|
|
|
|
toaster.danger(errorMsg);
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
viewsMap.current = {};
|
|
|
|
setNavigation([]);
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
}, [repoID, enableMetadata]);
|
|
|
|
|
|
|
|
const selectView = useCallback((view, isSelected) => {
|
|
|
|
if (isSelected) return;
|
|
|
|
const node = {
|
|
|
|
children: [],
|
2024-09-05 13:42:07 +08:00
|
|
|
path: '/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES + '/' + view._id,
|
2024-08-12 17:15:56 +08:00
|
|
|
isExpanded: false,
|
|
|
|
isLoaded: true,
|
|
|
|
isPreload: true,
|
|
|
|
object: {
|
|
|
|
file_tags: [],
|
|
|
|
id: PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES,
|
|
|
|
name: gettext('File extended properties'),
|
|
|
|
type: PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES,
|
|
|
|
isDir: () => false,
|
|
|
|
},
|
|
|
|
parentNode: {},
|
|
|
|
key: repoID,
|
|
|
|
view_id: view._id,
|
|
|
|
};
|
|
|
|
selectMetadataView(node);
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
}, [repoID, selectMetadataView]);
|
|
|
|
|
2024-08-21 17:14:57 +08:00
|
|
|
const addView = useCallback((name, type, successCallback, failCallback) => {
|
|
|
|
metadataAPI.addView(repoID, name, type).then(res => {
|
2024-08-12 17:15:56 +08:00
|
|
|
const view = res.data.view;
|
|
|
|
let newNavigation = navigation.slice(0);
|
|
|
|
newNavigation.push({ _id: view._id, type: 'view' });
|
|
|
|
viewsMap.current[view._id] = view;
|
|
|
|
setNavigation(newNavigation);
|
|
|
|
selectView(view);
|
|
|
|
successCallback && successCallback();
|
|
|
|
}).catch(error => {
|
|
|
|
failCallback && failCallback(error);
|
|
|
|
});
|
|
|
|
}, [navigation, repoID, viewsMap, selectView]);
|
|
|
|
|
2024-08-28 16:06:47 +08:00
|
|
|
const duplicateView = useCallback((viewId) => {
|
|
|
|
metadataAPI.duplicateView(repoID, viewId).then(res => {
|
|
|
|
const view = res.data.view;
|
|
|
|
let newNavigation = navigation.slice(0);
|
|
|
|
newNavigation.push({ _id: view._id, type: 'view' });
|
|
|
|
viewsMap.current[view._id] = view;
|
|
|
|
setNavigation(newNavigation);
|
|
|
|
selectView(view);
|
|
|
|
}).catch(error => {
|
|
|
|
const errorMsg = Utils.getErrorMsg(error);
|
|
|
|
toaster.danger(errorMsg);
|
|
|
|
});
|
|
|
|
}, [navigation, repoID, viewsMap, selectView]);
|
|
|
|
|
2024-08-12 17:15:56 +08:00
|
|
|
const deleteView = useCallback((viewId, isSelected) => {
|
|
|
|
metadataAPI.deleteView(repoID, viewId).then(res => {
|
|
|
|
const newNavigation = navigation.filter(item => item._id !== viewId);
|
|
|
|
delete viewsMap.current[viewId];
|
|
|
|
setNavigation(newNavigation);
|
|
|
|
if (isSelected) {
|
|
|
|
const currentViewIndex = navigation.findIndex(item => item._id === viewId);
|
|
|
|
const lastViewId = navigation[currentViewIndex - 1]._id;
|
|
|
|
const lastView = viewsMap.current[lastViewId];
|
|
|
|
selectView(lastView);
|
|
|
|
}
|
|
|
|
}).catch((error => {
|
|
|
|
const errorMsg = Utils.getErrorMsg(error);
|
|
|
|
toaster.danger(errorMsg);
|
|
|
|
}));
|
|
|
|
}, [repoID, navigation, selectView, viewsMap]);
|
|
|
|
|
|
|
|
const updateView = useCallback((viewId, update, successCallback, failCallback) => {
|
|
|
|
metadataAPI.modifyView(repoID, viewId, update).then(res => {
|
|
|
|
const currentView = viewsMap.current[viewId];
|
|
|
|
viewsMap.current[viewId] = { ...currentView, ...update };
|
2024-09-05 13:42:07 +08:00
|
|
|
setCount(n => n + 1);
|
2024-08-12 17:15:56 +08:00
|
|
|
successCallback && successCallback();
|
|
|
|
}).catch(error => {
|
|
|
|
failCallback && failCallback(error);
|
|
|
|
});
|
2024-09-05 13:42:07 +08:00
|
|
|
}, [repoID, viewsMap]);
|
2024-08-12 17:15:56 +08:00
|
|
|
|
|
|
|
const moveView = useCallback((sourceViewId, targetViewId) => {
|
|
|
|
metadataAPI.moveView(repoID, sourceViewId, targetViewId).then(res => {
|
|
|
|
const { navigation } = res.data;
|
|
|
|
setNavigation(navigation);
|
|
|
|
}).catch(error => {
|
|
|
|
const errorMsg = Utils.getErrorMsg(error);
|
|
|
|
toaster.danger(errorMsg);
|
|
|
|
});
|
|
|
|
}, [repoID]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<MetadataContext.Provider value={{
|
|
|
|
enableMetadata,
|
|
|
|
updateEnableMetadata,
|
|
|
|
showFirstView,
|
2024-08-27 13:34:26 +08:00
|
|
|
setShowFirstView,
|
2024-08-12 17:15:56 +08:00
|
|
|
navigation,
|
|
|
|
viewsMap: viewsMap.current,
|
|
|
|
selectView,
|
|
|
|
addView,
|
2024-08-28 16:06:47 +08:00
|
|
|
duplicateView,
|
2024-08-12 17:15:56 +08:00
|
|
|
deleteView,
|
|
|
|
updateView,
|
|
|
|
moveView,
|
|
|
|
}}>
|
|
|
|
{children}
|
|
|
|
</MetadataContext.Provider>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const useMetadata = () => {
|
|
|
|
const context = useContext(MetadataContext);
|
|
|
|
if (!context) {
|
|
|
|
throw new Error('\'MetadataContext\' is null');
|
|
|
|
}
|
|
|
|
return context;
|
|
|
|
};
|