diff --git a/client/packages/lowcoder-design/src/icons/index.tsx b/client/packages/lowcoder-design/src/icons/index.tsx index 687d3516b..a538cb9bb 100644 --- a/client/packages/lowcoder-design/src/icons/index.tsx +++ b/client/packages/lowcoder-design/src/icons/index.tsx @@ -1,4 +1,5 @@ export { ReactComponent as AppSnapshotIcon } from "./v1/app-snapshot.svg"; +export { ReactComponent as ArchiveIcon } from "./remix/archive-fill.svg"; export { ReactComponent as HookCompDropIcon } from "./v1/hook-comp-drop.svg"; export { ReactComponent as HookCompIcon } from "./v1/hook-comp.svg"; diff --git a/client/packages/lowcoder/src/api/appSnapshotApi.ts b/client/packages/lowcoder/src/api/appSnapshotApi.ts index 18e98678e..572576605 100644 --- a/client/packages/lowcoder/src/api/appSnapshotApi.ts +++ b/client/packages/lowcoder/src/api/appSnapshotApi.ts @@ -22,18 +22,34 @@ export interface AppSnapshotDslResp extends ApiResponse { class AppSnapshotApi extends Api { static createSnapshotURL = "/application/history-snapshots"; static snapshotsURL = (appId: string) => `/application/history-snapshots/${appId}`; + static archiveSnapshotsURL = (appId: string) => `/application/history-snapshots/archive/${appId}`; static snapshotDslURL = (appId: string, snapshotId: string) => `/application/history-snapshots/${appId}/${snapshotId}`; - + static archiveSnapshotDslURL = (appId: string, snapshotId: string) => + `/application/history-snapshots/archive/${appId}/${snapshotId}`; static createSnapshot(request: CreateSnapshotPayload): AxiosPromise { return Api.post(AppSnapshotApi.createSnapshotURL, request); } - static getSnapshots(appId: string, pagination: PaginationParam): AxiosPromise { + static getSnapshots( + appId: string, + pagination: PaginationParam, + archived?: boolean, + ): AxiosPromise { + if (archived) { + return Api.get(AppSnapshotApi.archiveSnapshotsURL(appId), pagination); + } return Api.get(AppSnapshotApi.snapshotsURL(appId), pagination); } - static getSnapshotDsl(appId: string, snapshotId: string): AxiosPromise { + static getSnapshotDsl( + appId: string, + snapshotId: string, + archived?: boolean, + ): AxiosPromise { + if (archived) { + return Api.get(AppSnapshotApi.archiveSnapshotDslURL(appId, snapshotId)); + } return Api.get(AppSnapshotApi.snapshotDslURL(appId, snapshotId)); } } diff --git a/client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx b/client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx index 9278ec326..f8c916404 100644 --- a/client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx +++ b/client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx @@ -193,7 +193,7 @@ let StepControlBasicComp = (function () { > {props.options.map((option, index) => ( (currentAppInfo); const isSnapshotDslLoading = useSelector(isAppSnapshotDslFetching); const compInstance = useRootCompInstance(appInfo, true, true); + const [activeTab, setActiveTab] = useState("recent"); + + const isArchivedSnapshot = useMemo(() => activeTab === 'archive', [activeTab]); - const fetchSnapshotList = (page: number, onSuccess?: (snapshots: AppSnapshotList) => void) => { - dispatch(setSelectSnapshotId("")); + const fetchSnapshotList = useCallback((page: number, onSuccess?: (snapshots: AppSnapshotList) => void) => { + dispatch(setSelectSnapshotId("", isArchivedSnapshot)); application && dispatch( fetchSnapshotsAction({ applicationId: application.applicationId, page: page, size: PAGE_SIZE, + archived: isArchivedSnapshot, onSuccess: onSuccess, }) ); - }; + }, [application, activeTab]); - useMount(() => { + + useEffect(() => { if (!application) { return; } @@ -174,12 +183,17 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo } return; } dispatch( - fetchSnapshotDslAction(application.applicationId, snapshots.list[0].snapshotId, (res) => { - setLatestDsl(res); - }) + fetchSnapshotDslAction( + application.applicationId, + snapshots.list[0].snapshotId, + isArchivedSnapshot, + (res) => { + setLatestDsl(res); + } + ) ); }); - }); + }, [application, activeTab]); useEffect(() => { currentDsl && @@ -193,7 +207,10 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo } return; } setSelectedItemKey(snapshotId); - dispatch(setSelectSnapshotId(snapshotId === CURRENT_ITEM_KEY ? "" : snapshotId)); + dispatch(setSelectSnapshotId( + snapshotId === CURRENT_ITEM_KEY ? "" : snapshotId, + isArchivedSnapshot, + )); if (snapshotId === CURRENT_ITEM_KEY) { setAppInfo(currentAppInfo); return; @@ -202,56 +219,108 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo } return; } dispatch( - fetchSnapshotDslAction(application.applicationId, snapshotId, (dsl) => { - setAppInfo((i) => ({ - ...i, - dsl: dsl.applicationsDsl, - moduleDsl: dsl.moduleDSL, - })); - }) + fetchSnapshotDslAction( + application.applicationId, + snapshotId, + isArchivedSnapshot, + (dsl) => { + setAppInfo((i) => ({ + ...i, + dsl: dsl.applicationsDsl, + moduleDsl: dsl.moduleDSL, + })); + } + ) ); }, - [application, currentAppInfo, dispatch, setAppInfo, selectedItemKey] + [application, currentAppInfo, dispatch, setAppInfo, selectedItemKey, activeTab] ); - let snapShotContent; - if (snapshotsFetching || (currentPage === 1 && appSnapshots.length > 0 && !latestDsl)) { - snapShotContent = ; - } else if (appSnapshots.length <= 0 || !application) { - snapShotContent = ; - } else { - let snapshotItems: SnapshotItemProps[] = appSnapshots.map((snapshot, index) => { - return { - selected: selectedItemKey === snapshot.snapshotId, - title: - `${ - !latestDslChanged && currentPage === 1 && index === 0 - ? trans("history.currentVersionWithBracket") - : "" - }` + getOperationDesc(snapshot.context), - timeInfo: timestampToHumanReadable(snapshot.createTime), - userName: snapshot.userName, - onClick: () => { - onSnapshotItemClick(snapshot.snapshotId); - }, - }; - }); - if (currentPage === 1 && latestDslChanged) { - snapshotItems = [ - { - selected: selectedItemKey === CURRENT_ITEM_KEY, - title: trans("history.currentVersion"), - timeInfo: trans("history.justNow"), - userName: user.username, + const snapShotContent = useMemo(() => { + if (snapshotsFetching || (currentPage === 1 && appSnapshots.length > 0 && !latestDsl)) { + return ; + } else if (appSnapshots.length <= 0 || !application) { + return ; + } else { + let snapshotItems: SnapshotItemProps[] = appSnapshots.map((snapshot, index) => { + return { + selected: selectedItemKey === snapshot.snapshotId, + title: + `${ + !latestDslChanged && currentPage === 1 && index === 0 + ? trans("history.currentVersionWithBracket") + : "" + }` + getOperationDesc(snapshot.context), + timeInfo: timestampToHumanReadable(snapshot.createTime), + userName: snapshot.userName, onClick: () => { - onSnapshotItemClick(CURRENT_ITEM_KEY); + onSnapshotItemClick(snapshot.snapshotId); + }, + }; + }); + if (currentPage === 1 && latestDslChanged) { + snapshotItems = [ + { + selected: selectedItemKey === CURRENT_ITEM_KEY, + title: trans("history.currentVersion"), + timeInfo: trans("history.justNow"), + userName: user.username, + onClick: () => { + onSnapshotItemClick(CURRENT_ITEM_KEY); + }, }, - }, - ...snapshotItems, - ]; + ...snapshotItems, + ]; + } + return ; } - snapShotContent = ; - } + }, [ + user, + snapshotsFetching, + currentPage, + appSnapshots, + latestDsl, + application, + selectedItemKey, + latestDslChanged, + onSnapshotItemClick, + ]); + + const TabContent = useMemo(() => ( + <> + + {snapShotContent} + + + { + setCurrentPage(page); + fetchSnapshotList(page); + }} + total={totalCount} + pageSize={PAGE_SIZE} + showSizeChanger={false} + /> + + + ), [headerHeight, footerHeight, snapShotContent, currentPage, totalCount]); + + const tabConfigs = useMemo(() => [ + { + key: "recent", + title: "Recent", + icon: , + content: TabContent, + }, + { + key: "archive", + title: "Archive", + icon: , + content: TabContent, + } + ], [TabContent]); return ( }> @@ -262,31 +331,13 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo } compInstance={compInstance} /> - - - {trans("history.history")} - { - dispatch(setShowAppSnapshot(false)); - }} - /> - - - {snapShotContent} - - - { - setCurrentPage(page); - fetchSnapshotList(page); - }} - total={totalCount} - pageSize={PAGE_SIZE} - showSizeChanger={false} - /> - + { + setActiveTab(key); + }} + tabsConfig={tabConfigs} + activeKey={activeTab} + /> ); diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/appSnapshotReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/appSnapshotReducer.ts index 27d63e13f..156f8fee5 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/appSnapshotReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/appSnapshotReducer.ts @@ -14,6 +14,7 @@ const initialState: AppSnapshotState = { showAppSnapshot: false, snapshotDslFetching: false, selectedSnapshotId: "", + isSelectedSnapshotIdArchived: false, }; const appSnapshotReducer = createReducer(initialState, { @@ -28,11 +29,12 @@ const appSnapshotReducer = createReducer(initialState, { }, [ReduxActionTypes.SET_SELECT_SNAPSHOT_ID]: ( state: AppSnapshotState, - action: ReduxAction<{ snapshotId: string }> + action: ReduxAction<{ snapshotId: string, archived?: boolean }> ): AppSnapshotState => { return { ...state, selectedSnapshotId: action.payload.snapshotId, + isSelectedSnapshotIdArchived: action.payload.archived, }; }, [ReduxActionTypes.FETCH_APP_SNAPSHOT_DSL]: (state: AppSnapshotState): AppSnapshotState => { @@ -115,6 +117,7 @@ export interface AppSnapshotState { appSnapshotCount: number; showAppSnapshot: boolean; selectedSnapshotId: string; + isSelectedSnapshotIdArchived?: boolean; } export default appSnapshotReducer; diff --git a/client/packages/lowcoder/src/redux/reduxActions/appSnapshotActions.ts b/client/packages/lowcoder/src/redux/reduxActions/appSnapshotActions.ts index 905d3a78d..3b52b1f19 100644 --- a/client/packages/lowcoder/src/redux/reduxActions/appSnapshotActions.ts +++ b/client/packages/lowcoder/src/redux/reduxActions/appSnapshotActions.ts @@ -11,10 +11,10 @@ export const setShowAppSnapshot = (show: boolean) => { }; }; -export const setSelectSnapshotId = (snapshotId: string) => { +export const setSelectSnapshotId = (snapshotId: string, archived?: boolean) => { return { type: ReduxActionTypes.SET_SELECT_SNAPSHOT_ID, - payload: { snapshotId: snapshotId }, + payload: { snapshotId: snapshotId, archived: archived }, }; }; @@ -33,6 +33,7 @@ export const createSnapshotAction = (payload: CreateSnapshotPayload) => { export type FetchSnapshotsPayload = { applicationId: string; + archived: boolean; onSuccess?: (snapshots: AppSnapshotList) => void; } & PaginationParam; @@ -46,17 +47,24 @@ export const fetchSnapshotsAction = (payload: FetchSnapshotsPayload) => { export type FetchSnapshotDslPayload = { applicationId: string; snapshotId: string; + archived?: boolean; onSuccess: (res: AppSnapshotDslInfo) => void; }; export const fetchSnapshotDslAction = ( appId: string, snapshotId: string, + archived: boolean, onSuccess: (res: AppSnapshotDslInfo) => void ): ReduxAction => { return { type: ReduxActionTypes.FETCH_APP_SNAPSHOT_DSL, - payload: { applicationId: appId, snapshotId: snapshotId, onSuccess: onSuccess }, + payload: { + applicationId: appId, + snapshotId: snapshotId, + archived: archived, + onSuccess: onSuccess, + }, }; }; @@ -64,12 +72,14 @@ export type RecoverSnapshotPayload = { applicationId: string; snapshotId: string; snapshotCreateTime: number; + isArchivedSnapshot?: boolean; }; export const recoverSnapshotAction = ( appId: string, snapshotId: string, - snapshotCreateTime: number + snapshotCreateTime: number, + isArchivedSnapshot?: boolean, ): ReduxAction => { return { type: ReduxActionTypes.RECOVER_APP_SNAPSHOT, @@ -77,6 +87,7 @@ export const recoverSnapshotAction = ( applicationId: appId, snapshotId: snapshotId, snapshotCreateTime: snapshotCreateTime, + isArchivedSnapshot, }, }; }; diff --git a/client/packages/lowcoder/src/redux/sagas/appSnapshotSagas.ts b/client/packages/lowcoder/src/redux/sagas/appSnapshotSagas.ts index 266beeb5d..a111d7e84 100644 --- a/client/packages/lowcoder/src/redux/sagas/appSnapshotSagas.ts +++ b/client/packages/lowcoder/src/redux/sagas/appSnapshotSagas.ts @@ -42,7 +42,11 @@ export function* fetchAppSnapshotsSaga(action: ReduxAction = yield call( AppSnapshotApi.getSnapshots, action.payload.applicationId, - { page: action.payload.page, size: action.payload.size } + { + page: action.payload.page, + size: action.payload.size, + }, + action.payload.archived, ); if (validateResponse(response)) { action.payload.onSuccess && action.payload.onSuccess(response.data.data); @@ -63,7 +67,8 @@ export function* fetchAppSnapshotDslSaga(action: ReduxAction = yield call( AppSnapshotApi.getSnapshotDsl, action.payload.applicationId, - action.payload.snapshotId + action.payload.snapshotId, + action.payload.archived, ); if (validateResponse(response)) { // replace dsl @@ -81,11 +86,12 @@ export function* fetchAppSnapshotDslSaga(action: ReduxAction) { try { - const { applicationId, snapshotId, snapshotCreateTime } = action.payload; + const { applicationId, snapshotId, snapshotCreateTime, isArchivedSnapshot } = action.payload; const response: AxiosResponse = yield call( AppSnapshotApi.getSnapshotDsl, applicationId, - snapshotId + snapshotId, + isArchivedSnapshot, ); if (validateResponse(response)) { // record history record diff --git a/client/packages/lowcoder/src/redux/selectors/appSnapshotSelector.ts b/client/packages/lowcoder/src/redux/selectors/appSnapshotSelector.ts index c2b7af89f..189139250 100644 --- a/client/packages/lowcoder/src/redux/selectors/appSnapshotSelector.ts +++ b/client/packages/lowcoder/src/redux/selectors/appSnapshotSelector.ts @@ -5,9 +5,13 @@ export const showAppSnapshotSelector = (state: AppState) => { }; export const getSelectedAppSnapshot = (state: AppState) => { - return state.ui.appSnapshot.appSnapshots.find( + const selectedSnapshot = state.ui.appSnapshot.appSnapshots.find( (s) => s.snapshotId === state.ui.appSnapshot.selectedSnapshotId ); + return { + selectedSnapshot, + isArchivedSnapshot: state.ui.appSnapshot.isSelectedSnapshotIdArchived, + } }; export const appSnapshotsSelector = (state: AppState) => {