Skip to content

Commit

Permalink
Merge pull request #1326 from lowcoder-org/archived_snapshots
Browse files Browse the repository at this point in the history
Show recent and archived snapshots for app
  • Loading branch information
FalkWolsky authored Nov 27, 2024
2 parents d12d09d + 28a7f2b commit 6827626
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 95 deletions.
1 change: 1 addition & 0 deletions client/packages/lowcoder-design/src/icons/index.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
22 changes: 19 additions & 3 deletions client/packages/lowcoder/src/api/appSnapshotApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApiResponse> {
return Api.post(AppSnapshotApi.createSnapshotURL, request);
}

static getSnapshots(appId: string, pagination: PaginationParam): AxiosPromise<AppSnapshotsResp> {
static getSnapshots(
appId: string,
pagination: PaginationParam,
archived?: boolean,
): AxiosPromise<AppSnapshotsResp> {
if (archived) {
return Api.get(AppSnapshotApi.archiveSnapshotsURL(appId), pagination);
}
return Api.get(AppSnapshotApi.snapshotsURL(appId), pagination);
}

static getSnapshotDsl(appId: string, snapshotId: string): AxiosPromise<AppSnapshotDslResp> {
static getSnapshotDsl(
appId: string,
snapshotId: string,
archived?: boolean,
): AxiosPromise<AppSnapshotDslResp> {
if (archived) {
return Api.get(AppSnapshotApi.archiveSnapshotDslURL(appId, snapshotId));
}
return Api.get(AppSnapshotApi.snapshotDslURL(appId, snapshotId));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ let StepControlBasicComp = (function () {
>
{props.options.map((option, index) => (
<Steps.Step
style={{minWidth:props.minHorizontalWidth || '100%'}}
style={{minWidth:props.minHorizontalWidth || 'auto'}}
key={index}
title={option.label}
subTitle={option.subTitle}
Expand Down
5 changes: 3 additions & 2 deletions client/packages/lowcoder/src/pages/common/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ export default function Header(props: HeaderProps) {
const applicationId = useApplicationId();
const dispatch = useDispatch();
const showAppSnapshot = useSelector(showAppSnapshotSelector);
const selectedSnapshot = useSelector(getSelectedAppSnapshot);
const {selectedSnapshot, isArchivedSnapshot} = useSelector(getSelectedAppSnapshot);
const { appType } = useContext(ExternalEditorContext);
const [editName, setEditName] = useState(false);
const [editing, setEditing] = useState(false);
Expand Down Expand Up @@ -512,7 +512,8 @@ export default function Header(props: HeaderProps) {
recoverSnapshotAction(
application.applicationId,
selectedSnapshot.snapshotId,
selectedSnapshot.createTime
selectedSnapshot.createTime,
isArchivedSnapshot,
)
);
},
Expand Down
209 changes: 130 additions & 79 deletions client/packages/lowcoder/src/pages/editor/appSnapshot.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import { CloseIcon } from "lowcoder-design";
import { CloseIcon, ArchiveIcon, Tabs } from "lowcoder-design";
import { AppSnapshotIcon } from "lowcoder-design";
import {
fetchSnapshotDslAction,
Expand All @@ -9,7 +9,7 @@ import {
setShowAppSnapshot,
} from "redux/reduxActions/appSnapshotActions";
import dayjs from "dayjs";
import { Suspense, lazy, useCallback, useEffect, useState } from "react";
import { Suspense, lazy, useCallback, useEffect, useMemo, useState } from "react";
import { currentApplication } from "redux/selectors/applicationSelector";
import {
appSnapshotCountSelector,
Expand Down Expand Up @@ -88,6 +88,10 @@ const StyledSnapshotIcon = styled(AppSnapshotIcon)`
margin-right: 4px;
`;

const StyledSnapshotArchiveIcon = styled(ArchiveIcon)`
margin-right: 4px;
`;

const StyledCloseIcon = styled(CloseIcon)`
margin-left: auto;
cursor: pointer;
Expand Down Expand Up @@ -151,21 +155,26 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo }
const [appInfo, setAppInfo] = useState<AppSummaryInfo>(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;
}
Expand All @@ -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 &&
Expand All @@ -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;
Expand All @@ -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 = <Skeleton style={{ padding: "0 16px" }} active paragraph={{ rows: 10 }} />;
} else if (appSnapshots.length <= 0 || !application) {
snapShotContent = <EmptyContent text={trans("history.emptyHistory")} />;
} 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 <Skeleton style={{ padding: "0 16px" }} active paragraph={{ rows: 10 }} />;
} else if (appSnapshots.length <= 0 || !application) {
return <EmptyContent text={trans("history.emptyHistory")} />;
} 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 <SnapshotList items={snapshotItems} />;
}
snapShotContent = <SnapshotList items={snapshotItems} />;
}
}, [
user,
snapshotsFetching,
currentPage,
appSnapshots,
latestDsl,
application,
selectedItemKey,
latestDslChanged,
onSnapshotItemClick,
]);

const TabContent = useMemo(() => (
<>
<ScrollBar height={`calc(100% - ${headerHeight + footerHeight}px)`}>
<SnapshotContent>{snapShotContent}</SnapshotContent>
</ScrollBar>
<SnapshotFooter>
<TacoPagination
current={currentPage}
showLessItems
onChange={(page) => {
setCurrentPage(page);
fetchSnapshotList(page);
}}
total={totalCount}
pageSize={PAGE_SIZE}
showSizeChanger={false}
/>
</SnapshotFooter>
</>
), [headerHeight, footerHeight, snapShotContent, currentPage, totalCount]);

const tabConfigs = useMemo(() => [
{
key: "recent",
title: "Recent",
icon: <StyledSnapshotIcon />,
content: TabContent,
},
{
key: "archive",
title: "Archive",
icon: <StyledSnapshotArchiveIcon />,
content: TabContent,
}
], [TabContent]);

return (
<Suspense fallback={<EditorSkeletonView />}>
Expand All @@ -262,31 +331,13 @@ export const AppSnapshot = React.memo((props: { currentAppInfo: AppSummaryInfo }
compInstance={compInstance}
/>
<AppSnapshotPanel>
<SnapshotHeader>
<StyledSnapshotIcon />
<span>{trans("history.history")}</span>
<StyledCloseIcon
onClick={() => {
dispatch(setShowAppSnapshot(false));
}}
/>
</SnapshotHeader>
<ScrollBar height={`calc(100% - ${headerHeight + footerHeight}px)`}>
<SnapshotContent>{snapShotContent}</SnapshotContent>
</ScrollBar>
<SnapshotFooter>
<TacoPagination
current={currentPage}
showLessItems
onChange={(page) => {
setCurrentPage(page);
fetchSnapshotList(page);
}}
total={totalCount}
pageSize={PAGE_SIZE}
showSizeChanger={false}
/>
</SnapshotFooter>
<Tabs
onChange={(key) => {
setActiveTab(key);
}}
tabsConfig={tabConfigs}
activeKey={activeTab}
/>
</AppSnapshotPanel>
</Suspense>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const initialState: AppSnapshotState = {
showAppSnapshot: false,
snapshotDslFetching: false,
selectedSnapshotId: "",
isSelectedSnapshotIdArchived: false,
};

const appSnapshotReducer = createReducer(initialState, {
Expand All @@ -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 => {
Expand Down Expand Up @@ -115,6 +117,7 @@ export interface AppSnapshotState {
appSnapshotCount: number;
showAppSnapshot: boolean;
selectedSnapshotId: string;
isSelectedSnapshotIdArchived?: boolean;
}

export default appSnapshotReducer;
Loading

0 comments on commit 6827626

Please sign in to comment.