slice-machine-ui 1.23.2-beta.10 → 1.23.2-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ChangesEmptyState/OfflinePage.tsx +1 -1
- package/components/ChangesItems/ChangesItems.tsx +6 -2
- package/components/CustomTypeTable/changesPage.tsx +12 -7
- package/components/DeleteSliceModal/index.tsx +3 -0
- package/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx +4 -0
- package/components/Forms/CreateSliceModal/CreateSliceModal.tsx +3 -1
- package/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx +6 -1
- package/components/Forms/RenameSliceModal/RenameSliceModal.tsx +3 -0
- package/components/LoginModal/index.tsx +40 -2
- package/components/Navigation/ChangesItem.tsx +117 -0
- package/components/Navigation/Environment.tsx +51 -4
- package/components/Navigation/index.css.ts +1 -2
- package/components/Navigation/index.test.tsx +5 -85
- package/components/Navigation/index.tsx +3 -8
- package/lib/builders/SliceBuilder/index.tsx +3 -3
- package/lib/models/common/ModelStatus/index.ts +25 -0
- package/out/404.html +1 -1
- package/out/_next/static/chunks/25-3aa4d4b24167d340.js +1 -0
- package/out/_next/static/chunks/{318-99b5914b60bf1d17.js → 318-19dbcae10cdb6408.js} +1 -1
- package/out/_next/static/chunks/727-de03055e40815a3a.js +1 -0
- package/out/_next/static/chunks/{799-b616401fe0dafdeb.js → 799-0af1215eb2b3c17e.js} +1 -1
- package/out/_next/static/chunks/904-40686b0426716111.js +1 -0
- package/out/_next/static/chunks/pages/{_app-5ec41fc53c1f6405.js → _app-bca191df4ac7078e.js} +24 -24
- package/out/_next/static/chunks/pages/{changelog-02cd80dad6f40878.js → changelog-cb1df3a70b20f1b8.js} +1 -1
- package/out/_next/static/chunks/pages/changes-ccfd715c2bdc4f83.js +1 -0
- package/out/_next/static/chunks/pages/{labs-39b50541a4c370bf.js → labs-2adfb5127f9c4081.js} +1 -1
- package/out/_next/static/chunks/pages/{settings-879686e3a8759dc3.js → settings-94bcd7422e8b237d.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-c498e9c80cae8cce.js → simulator-aaf65fbec8f5839c.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-90bb2ea76d2abf42.js +1 -0
- package/out/_next/static/chunks/pages/slices-e0ce615c38e8338d.js +1 -0
- package/out/_next/static/css/{3c26dbbbd8f4be12.css → 39e089aad1742a3a.css} +1 -1
- package/out/_next/static/{Dz7wwTNVdXvoAjySXz7mE → dS_xZnzzna6Y6uwcTgf90}/_buildManifest.js +1 -1
- package/out/changelog.html +1 -1
- package/out/changes.html +1 -1
- package/out/custom-types/[customTypeId].html +1 -1
- package/out/custom-types.html +1 -1
- package/out/index.html +1 -1
- package/out/labs.html +1 -1
- package/out/page-types/[pageTypeId].html +1 -1
- package/out/settings.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
- package/out/slices.html +1 -1
- package/package.json +3 -3
- package/pages/_app.tsx +34 -32
- package/pages/changes.tsx +27 -41
- package/src/components/SideNav/SideNav.css.ts +1 -1
- package/src/components/SideNav/SideNav.tsx +9 -5
- package/src/components/SideNav/SideNavEnvironmentSelector.tsx +31 -11
- package/src/features/autoSave/AutoSaveStatusIndicator.tsx +4 -4
- package/src/features/customTypes/customTypesBuilder/CustomTypeProvider.tsx +11 -10
- package/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx +2 -2
- package/src/features/environments/actions/setEnvironment.ts +3 -3
- package/src/features/slices/sliceBuilder/SliceBuilderProvider.tsx +12 -10
- package/src/features/sync/AutoSyncProvider.tsx +252 -0
- package/src/features/sync/components/AutoSyncStatusIndicator.module.css +17 -0
- package/src/features/sync/components/AutoSyncStatusIndicator.module.css.d.ts +5 -0
- package/src/features/sync/components/AutoSyncStatusIndicator.tsx +58 -0
- package/src/features/sync/fetchUnSyncChanges.ts +40 -0
- package/src/features/sync/getUnSyncChanges.ts +144 -0
- package/src/features/sync/useUnSyncChanges.ts +32 -0
- package/src/hooks/useActionQueue.tsx +177 -0
- package/src/hooks/useAuthStatus.ts +12 -0
- package/src/icons/SyncFailed.tsx +30 -0
- package/src/icons/Synced.tsx +19 -0
- package/src/icons/Syncing.tsx +23 -0
- package/test/__testutils__/index.tsx +8 -5
- package/components/Navigation/ChangesListItem.tsx +0 -74
- package/components/Navigation/ChangesRightElement.tsx +0 -33
- package/out/_next/static/chunks/25-01f422cea8066349.js +0 -1
- package/out/_next/static/chunks/727-f8fefdd3e50cab2a.js +0 -1
- package/out/_next/static/chunks/904-4653b7a849f4db94.js +0 -1
- package/out/_next/static/chunks/pages/changes-8e9da8fbb809aad3.js +0 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-9b893c62b2f2fe4a.js +0 -1
- package/out/_next/static/chunks/pages/slices-4522d604117d8c66.js +0 -1
- package/src/features/autoSave/useAutoSave.tsx +0 -177
- package/src/features/legacyState/actions/getLegacySliceMachineState.ts +0 -53
- package/src/features/legacyState/useLegacySliceMachineState.ts +0 -7
- package/src/hooks/useModelStatus.ts +0 -78
- package/src/hooks/useUnSyncChanges.ts +0 -88
- package/test/components/LoginModal.test.tsx +0 -67
- package/test/src/hooks/useModelStatus.test.ts +0 -115
- /package/out/_next/static/{Dz7wwTNVdXvoAjySXz7mE → dS_xZnzzna6Y6uwcTgf90}/_ssgManifest.js +0 -0
- /package/src/features/{changes → sync}/actions/pushChanges.ts +0 -0
- /package/src/features/{changes → sync}/actions/trackPushChangesSuccess.ts +0 -0
|
@@ -28,7 +28,7 @@ export const OfflinePage = () => {
|
|
|
28
28
|
<Text
|
|
29
29
|
sx={{ fontSize: "13px", lineHeight: "24px", textAlign: "center" }}
|
|
30
30
|
>
|
|
31
|
-
|
|
31
|
+
Connect to the internet to sync your changes.
|
|
32
32
|
</Text>
|
|
33
33
|
</Flex>
|
|
34
34
|
</Flex>
|
|
@@ -6,19 +6,23 @@ import { ChangesSectionHeader } from "@components/ChangesSectionHeader";
|
|
|
6
6
|
import { CustomTypeTable } from "@components/CustomTypeTable/changesPage";
|
|
7
7
|
import Grid from "@components/Grid";
|
|
8
8
|
import { ComponentUI } from "@lib/models/common/ComponentUI";
|
|
9
|
-
import { ModelStatusInformation } from "@src/hooks/useModelStatus";
|
|
10
9
|
import ScreenshotChangesModal from "@components/ScreenshotChangesModal";
|
|
11
10
|
import { countMissingScreenshots } from "@src/domain/slice";
|
|
12
11
|
import { useScreenshotChangesModal } from "@src/hooks/useScreenshotChangesModal";
|
|
13
12
|
import { ModelStatus } from "@lib/models/common/ModelStatus";
|
|
14
13
|
import { LocalOrRemoteCustomType } from "@lib/models/common/ModelData";
|
|
15
14
|
import { SharedSliceCard } from "@src/features/slices/sliceCards/SharedSliceCard";
|
|
15
|
+
import { AuthStatus } from "@src/modules/userContext/types";
|
|
16
|
+
import { ModelsStatuses } from "@src/features/sync/getUnSyncChanges";
|
|
16
17
|
|
|
17
18
|
import { DevCollaborationExperiment } from "./DevCollaborationExperiment";
|
|
18
19
|
|
|
19
|
-
interface ChangesItemsProps
|
|
20
|
+
interface ChangesItemsProps {
|
|
20
21
|
unSyncedCustomTypes: LocalOrRemoteCustomType[];
|
|
21
22
|
unSyncedSlices: ComponentUI[];
|
|
23
|
+
modelsStatuses: ModelsStatuses;
|
|
24
|
+
authStatus: AuthStatus;
|
|
25
|
+
isOnline: boolean;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export const ChangesItems: React.FC<ChangesItemsProps> = ({
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { Box, Text } from "theme-ui";
|
|
4
|
-
import { ModelStatusInformation } from "@src/hooks/useModelStatus";
|
|
5
4
|
import { CustomTypeSM } from "@lib/models/common/CustomType";
|
|
6
5
|
import { ModelStatus } from "@lib/models/common/ModelStatus";
|
|
7
6
|
import {
|
|
@@ -10,18 +9,26 @@ import {
|
|
|
10
9
|
} from "@lib/models/common/ModelData";
|
|
11
10
|
import { StatusBadge } from "@src/features/changes/StatusBadge";
|
|
12
11
|
import { CUSTOM_TYPES_CONFIG } from "@src/features/customTypes/customTypesConfig";
|
|
12
|
+
import { AuthStatus } from "@src/modules/userContext/types";
|
|
13
|
+
import { ModelsStatuses } from "@src/features/sync/getUnSyncChanges";
|
|
13
14
|
|
|
14
|
-
interface CustomTypeTableProps
|
|
15
|
+
interface CustomTypeTableProps {
|
|
15
16
|
customTypes: LocalOrRemoteCustomType[];
|
|
17
|
+
modelsStatuses: ModelsStatuses;
|
|
18
|
+
authStatus: AuthStatus;
|
|
19
|
+
isOnline: boolean;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
const firstColumnWidth = "40%";
|
|
19
23
|
const secondColumnWidth = "40%";
|
|
20
24
|
const thirdColumnWidth = "20%";
|
|
21
25
|
|
|
22
|
-
const CustomTypeChangeRow: React.FC<
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
const CustomTypeChangeRow: React.FC<{
|
|
27
|
+
ct: CustomTypeSM;
|
|
28
|
+
status: ModelStatus;
|
|
29
|
+
authStatus: AuthStatus;
|
|
30
|
+
isOnline: boolean;
|
|
31
|
+
}> = ({ ct, status, authStatus, isOnline }) => {
|
|
25
32
|
return (
|
|
26
33
|
<>
|
|
27
34
|
<Box as={"td"} style={{ width: firstColumnWidth }}>
|
|
@@ -80,7 +87,6 @@ export const CustomTypeTable: React.FC<CustomTypeTableProps> = ({
|
|
|
80
87
|
status={modelsStatuses.customTypes[customType.local.id]}
|
|
81
88
|
authStatus={authStatus}
|
|
82
89
|
isOnline={isOnline}
|
|
83
|
-
modelsStatuses={modelsStatuses}
|
|
84
90
|
key={customType.local.id}
|
|
85
91
|
/>
|
|
86
92
|
</tr>
|
|
@@ -97,7 +103,6 @@ export const CustomTypeTable: React.FC<CustomTypeTableProps> = ({
|
|
|
97
103
|
status={modelsStatuses.customTypes[customType.remote.id]}
|
|
98
104
|
authStatus={authStatus}
|
|
99
105
|
isOnline={isOnline}
|
|
100
|
-
modelsStatuses={modelsStatuses}
|
|
101
106
|
key={customType.remote.id}
|
|
102
107
|
/>
|
|
103
108
|
</tr>
|
|
@@ -7,6 +7,7 @@ import useSliceMachineActions from "@src/modules/useSliceMachineActions";
|
|
|
7
7
|
import Card from "@components/Card";
|
|
8
8
|
import { Button } from "@components/Button";
|
|
9
9
|
import { deleteSlice } from "@src/features/slices/actions/deleteSlice";
|
|
10
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
10
11
|
|
|
11
12
|
type DeleteSliceModalProps = {
|
|
12
13
|
isOpen: boolean;
|
|
@@ -21,6 +22,7 @@ export const DeleteSliceModal: React.FunctionComponent<
|
|
|
21
22
|
> = ({ sliceId, sliceName, libName, isOpen, onClose }) => {
|
|
22
23
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
23
24
|
const { deleteSliceSuccess } = useSliceMachineActions();
|
|
25
|
+
const { syncChanges } = useAutoSync();
|
|
24
26
|
const { theme } = useThemeUI();
|
|
25
27
|
|
|
26
28
|
const onDelete = async () => {
|
|
@@ -32,6 +34,7 @@ export const DeleteSliceModal: React.FunctionComponent<
|
|
|
32
34
|
libraryID: libName,
|
|
33
35
|
onSuccess: () => {
|
|
34
36
|
deleteSliceSuccess(sliceId, libName);
|
|
37
|
+
syncChanges();
|
|
35
38
|
},
|
|
36
39
|
});
|
|
37
40
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "@src/features/customTypes/actions/createCustomType";
|
|
22
22
|
import { CUSTOM_TYPES_CONFIG } from "@src/features/customTypes/customTypesConfig";
|
|
23
23
|
import { getFormat } from "@src/domain/customType";
|
|
24
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
24
25
|
|
|
25
26
|
import { InputBox } from "../components/InputBox";
|
|
26
27
|
import { SelectRepeatable } from "../components/SelectRepeatable";
|
|
@@ -58,6 +59,7 @@ export const CreateCustomTypeModal: React.FC<CreateCustomTypeModalProps> = ({
|
|
|
58
59
|
);
|
|
59
60
|
const customTypesMessages = CUSTOM_TYPES_MESSAGES[format];
|
|
60
61
|
const [isIdFieldPristine, setIsIdFieldPristine] = useState(true);
|
|
62
|
+
const { syncChanges } = useAutoSync();
|
|
61
63
|
const router = useRouter();
|
|
62
64
|
|
|
63
65
|
const onSubmit = async ({ id, label, repeatable }: FormValues) => {
|
|
@@ -86,6 +88,8 @@ export const CreateCustomTypeModal: React.FC<CreateCustomTypeModalProps> = ({
|
|
|
86
88
|
}
|
|
87
89
|
: undefined,
|
|
88
90
|
});
|
|
91
|
+
|
|
92
|
+
syncChanges();
|
|
89
93
|
},
|
|
90
94
|
});
|
|
91
95
|
|
|
@@ -9,6 +9,7 @@ import ModalFormCard from "@components/ModalFormCard";
|
|
|
9
9
|
import { createSlice } from "@src/features/slices/actions/createSlice";
|
|
10
10
|
import useSliceMachineActions from "@src/modules/useSliceMachineActions";
|
|
11
11
|
import { getState } from "@src/apiClient";
|
|
12
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
12
13
|
|
|
13
14
|
import { validateSliceModalValues } from "../formsValidator";
|
|
14
15
|
import { InputBox } from "../components/InputBox";
|
|
@@ -30,6 +31,7 @@ export const CreateSliceModal: FC<CreateSliceModalProps> = ({
|
|
|
30
31
|
}) => {
|
|
31
32
|
const { createSliceSuccess } = useSliceMachineActions();
|
|
32
33
|
const [isCreatingSlice, setIsCreatingSlice] = useState(false);
|
|
34
|
+
const { syncChanges } = useAutoSync();
|
|
33
35
|
|
|
34
36
|
const onSubmit = async (values: FormValues) => {
|
|
35
37
|
const sliceName = values.sliceName;
|
|
@@ -45,8 +47,8 @@ export const CreateSliceModal: FC<CreateSliceModalProps> = ({
|
|
|
45
47
|
const serverState = await getState();
|
|
46
48
|
// Update Redux store
|
|
47
49
|
createSliceSuccess(serverState.libraries);
|
|
48
|
-
|
|
49
50
|
onSuccess(newSlice, libraryName);
|
|
51
|
+
syncChanges();
|
|
50
52
|
},
|
|
51
53
|
});
|
|
52
54
|
};
|
|
@@ -13,6 +13,7 @@ import { CustomTypeFormat } from "@slicemachine/manager";
|
|
|
13
13
|
import { CUSTOM_TYPES_MESSAGES } from "@src/features/customTypes/customTypesMessages";
|
|
14
14
|
|
|
15
15
|
import { renameCustomType } from "@src/features/customTypes/actions/renameCustomType";
|
|
16
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
16
17
|
|
|
17
18
|
interface RenameCustomTypeModalProps {
|
|
18
19
|
isChangesLocal: boolean;
|
|
@@ -32,6 +33,7 @@ export const RenameCustomTypeModal: React.FC<RenameCustomTypeModalProps> = ({
|
|
|
32
33
|
const customTypeName = customType?.label ?? "";
|
|
33
34
|
const customTypeId = customType?.id ?? "";
|
|
34
35
|
const { renameCustomTypeSuccess } = useSliceMachineActions();
|
|
36
|
+
const { syncChanges } = useAutoSync();
|
|
35
37
|
|
|
36
38
|
const [isRenaming, setIsRenaming] = useState(false);
|
|
37
39
|
|
|
@@ -46,7 +48,10 @@ export const RenameCustomTypeModal: React.FC<RenameCustomTypeModalProps> = ({
|
|
|
46
48
|
await renameCustomType({
|
|
47
49
|
model: customType,
|
|
48
50
|
newLabel: values.customTypeName,
|
|
49
|
-
onSuccess:
|
|
51
|
+
onSuccess: (renamedCustomType) => {
|
|
52
|
+
renameCustomTypeSuccess(renamedCustomType);
|
|
53
|
+
syncChanges();
|
|
54
|
+
},
|
|
50
55
|
});
|
|
51
56
|
}
|
|
52
57
|
setIsRenaming(false);
|
|
@@ -6,6 +6,7 @@ import { SliceMachineStoreType } from "@src/redux/type";
|
|
|
6
6
|
import { getLibraries, getRemoteSlices } from "@src/modules/slices";
|
|
7
7
|
import { ComponentUI } from "@lib/models/common/ComponentUI";
|
|
8
8
|
import { renameSlice } from "@src/features/slices/actions/renameSlice";
|
|
9
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
9
10
|
|
|
10
11
|
import { InputBox } from "../components/InputBox";
|
|
11
12
|
import ModalFormCard from "../../ModalFormCard";
|
|
@@ -24,6 +25,7 @@ export const RenameSliceModal: React.FC<RenameSliceModalProps> = ({
|
|
|
24
25
|
onClose,
|
|
25
26
|
}) => {
|
|
26
27
|
const { renameSliceSuccess } = useSliceMachineActions();
|
|
28
|
+
const { syncChanges } = useAutoSync();
|
|
27
29
|
const { localLibs, remoteLibs } = useSelector(
|
|
28
30
|
(store: SliceMachineStoreType) => ({
|
|
29
31
|
localLibs: getLibraries(store),
|
|
@@ -39,6 +41,7 @@ export const RenameSliceModal: React.FC<RenameSliceModalProps> = ({
|
|
|
39
41
|
newSliceName: values.sliceName,
|
|
40
42
|
onSuccess: (renamedSlice) => {
|
|
41
43
|
renameSliceSuccess(renamedSlice.from, renamedSlice.model);
|
|
44
|
+
syncChanges();
|
|
42
45
|
},
|
|
43
46
|
});
|
|
44
47
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
Text,
|
|
12
12
|
} from "theme-ui";
|
|
13
13
|
import SliceMachineModal from "@components/SliceMachineModal";
|
|
14
|
-
import { checkAuthStatus, startAuth } from "@src/apiClient";
|
|
14
|
+
import { checkAuthStatus, getState, startAuth } from "@src/apiClient";
|
|
15
15
|
import { buildEndpoints } from "@lib/prismic/endpoints";
|
|
16
16
|
import { startPolling } from "@lib/utils/poll";
|
|
17
17
|
import { CheckAuthStatusResponse } from "@models/common/Auth";
|
|
@@ -25,6 +25,12 @@ import { getEnvironment } from "@src/modules/environment";
|
|
|
25
25
|
import useSliceMachineActions from "@src/modules/useSliceMachineActions";
|
|
26
26
|
import preferWroomBase from "@lib/utils/preferWroomBase";
|
|
27
27
|
import { ToasterType } from "@src/modules/toaster";
|
|
28
|
+
import { getUnSyncedChanges } from "@src/features/sync/getUnSyncChanges";
|
|
29
|
+
import { normalizeFrontendCustomTypes } from "@lib/models/common/normalizers/customType";
|
|
30
|
+
import { normalizeFrontendSlices } from "@lib/models/common/normalizers/slices";
|
|
31
|
+
import { AuthStatus } from "@src/modules/userContext/types";
|
|
32
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
33
|
+
import { getActiveEnvironment } from "@src/features/environments/actions/getActiveEnvironment";
|
|
28
34
|
|
|
29
35
|
interface ValidAuthStatus extends CheckAuthStatusResponse {
|
|
30
36
|
status: "ok";
|
|
@@ -42,7 +48,7 @@ const LoginModal: React.FunctionComponent = () => {
|
|
|
42
48
|
env: getEnvironment(store),
|
|
43
49
|
}),
|
|
44
50
|
);
|
|
45
|
-
|
|
51
|
+
const { syncChanges } = useAutoSync();
|
|
46
52
|
const { closeModals, startLoadingLogin, stopLoadingLogin, openToaster } =
|
|
47
53
|
useSliceMachineActions();
|
|
48
54
|
|
|
@@ -72,6 +78,38 @@ const LoginModal: React.FunctionComponent = () => {
|
|
|
72
78
|
openToaster("Logged in", ToasterType.SUCCESS);
|
|
73
79
|
stopLoadingLogin();
|
|
74
80
|
closeModals();
|
|
81
|
+
|
|
82
|
+
const serverState = await getState();
|
|
83
|
+
const slices = normalizeFrontendSlices(
|
|
84
|
+
serverState.libraries,
|
|
85
|
+
serverState.remoteSlices,
|
|
86
|
+
);
|
|
87
|
+
const customTypes = Object.values(
|
|
88
|
+
normalizeFrontendCustomTypes(
|
|
89
|
+
serverState.customTypes,
|
|
90
|
+
serverState.remoteCustomTypes,
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
const { changedCustomTypes, changedSlices } = getUnSyncedChanges({
|
|
94
|
+
authStatus: AuthStatus.AUTHORIZED,
|
|
95
|
+
customTypes,
|
|
96
|
+
isOnline: true,
|
|
97
|
+
libraries: serverState.libraries,
|
|
98
|
+
slices,
|
|
99
|
+
});
|
|
100
|
+
const { activeEnvironment } = await getActiveEnvironment();
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
activeEnvironment?.kind === "dev" &&
|
|
104
|
+
(changedCustomTypes.length > 0 || changedSlices.length > 0)
|
|
105
|
+
) {
|
|
106
|
+
syncChanges({
|
|
107
|
+
environment: activeEnvironment,
|
|
108
|
+
loggedIn: true,
|
|
109
|
+
changedCustomTypes,
|
|
110
|
+
changedSlices,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
75
113
|
} catch (e) {
|
|
76
114
|
stopLoadingLogin();
|
|
77
115
|
openToaster("Login failed", ToasterType.ERROR);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type FC } from "react";
|
|
2
|
+
import { useRouter } from "next/router";
|
|
3
|
+
import { Box, Button, Text } from "@prismicio/editor-ui";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
HoverCard,
|
|
7
|
+
HoverCardCloseButton,
|
|
8
|
+
HoverCardDescription,
|
|
9
|
+
HoverCardMedia,
|
|
10
|
+
HoverCardTitle,
|
|
11
|
+
} from "@src/components/HoverCard";
|
|
12
|
+
import useSliceMachineActions from "@src/modules/useSliceMachineActions";
|
|
13
|
+
import { useNetwork } from "@src/hooks/useNetwork";
|
|
14
|
+
import { useAuthStatus } from "@src/hooks/useAuthStatus";
|
|
15
|
+
import { useUnSyncChanges } from "@src/features/sync/useUnSyncChanges";
|
|
16
|
+
import { AuthStatus } from "@src/modules/userContext/types";
|
|
17
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
18
|
+
import { AutoSyncStatusIndicator } from "@src/features/sync/components/AutoSyncStatusIndicator";
|
|
19
|
+
|
|
20
|
+
export const ChangesItem: FC = () => {
|
|
21
|
+
const { setSeenChangesToolTip } = useSliceMachineActions();
|
|
22
|
+
const open = useOpenChangesHoverCard();
|
|
23
|
+
const router = useRouter();
|
|
24
|
+
const { autoSyncStatus } = useAutoSync();
|
|
25
|
+
|
|
26
|
+
const onClose = () => {
|
|
27
|
+
setSeenChangesToolTip();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<HoverCard
|
|
32
|
+
open={open}
|
|
33
|
+
openDelay={3000}
|
|
34
|
+
onClose={onClose}
|
|
35
|
+
side="right"
|
|
36
|
+
sideOffset={24}
|
|
37
|
+
collisionPadding={280}
|
|
38
|
+
trigger={
|
|
39
|
+
<Box padding={{ bottom: 24 }}>
|
|
40
|
+
{autoSyncStatus === "failed" ||
|
|
41
|
+
autoSyncStatus === "synced" ||
|
|
42
|
+
autoSyncStatus === "syncing" ? (
|
|
43
|
+
<AutoSyncStatusIndicator autoSyncStatus={autoSyncStatus} />
|
|
44
|
+
) : (
|
|
45
|
+
// TODO(DT-1942): This should be a Button with a link component for
|
|
46
|
+
// accessibility
|
|
47
|
+
<Button
|
|
48
|
+
color="grey"
|
|
49
|
+
onClick={() => {
|
|
50
|
+
void router.push("/changes");
|
|
51
|
+
}}
|
|
52
|
+
sx={{ width: "100%" }}
|
|
53
|
+
>
|
|
54
|
+
<Box alignItems="center" gap={4}>
|
|
55
|
+
<Text variant="bold">Review changes</Text> <ChangesCount />
|
|
56
|
+
</Box>
|
|
57
|
+
</Button>
|
|
58
|
+
)}
|
|
59
|
+
</Box>
|
|
60
|
+
}
|
|
61
|
+
>
|
|
62
|
+
<HoverCardTitle>Push your changes</HoverCardTitle>
|
|
63
|
+
<HoverCardMedia component="image" src="/push.png" />
|
|
64
|
+
<HoverCardDescription>
|
|
65
|
+
When you click Save, your changes are saved locally. Then, you can push
|
|
66
|
+
your models to Prismic from the Changes page.
|
|
67
|
+
</HoverCardDescription>
|
|
68
|
+
<HoverCardCloseButton>Got it</HoverCardCloseButton>
|
|
69
|
+
</HoverCard>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const ChangesCount: FC = () => {
|
|
74
|
+
const isOnline = useNetwork();
|
|
75
|
+
const authStatus = useAuthStatus();
|
|
76
|
+
const { unSyncedSlices, unSyncedCustomTypes } = useUnSyncChanges();
|
|
77
|
+
const numberOfChanges = unSyncedSlices.length + unSyncedCustomTypes.length;
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
!isOnline ||
|
|
81
|
+
authStatus === AuthStatus.UNAUTHORIZED ||
|
|
82
|
+
authStatus === AuthStatus.FORBIDDEN
|
|
83
|
+
) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (numberOfChanges === 0) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const formattedNumberOfChanges = numberOfChanges > 9 ? "+9" : numberOfChanges;
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Box padding={{ inline: 6 }} borderRadius={10} backgroundColor="grey5">
|
|
95
|
+
<Text color="grey11" variant="small">
|
|
96
|
+
{formattedNumberOfChanges}
|
|
97
|
+
</Text>
|
|
98
|
+
</Box>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// TODO(DT-1925): Reactivate this feature
|
|
103
|
+
const useOpenChangesHoverCard = () => {
|
|
104
|
+
// const { hasSeenChangesToolTip, hasSeenSimulatorToolTip } = useSelector(
|
|
105
|
+
// (store: SliceMachineStoreType) => ({
|
|
106
|
+
// hasSeenChangesToolTip: userHasSeenChangesToolTip(store),
|
|
107
|
+
// hasSeenSimulatorToolTip: userHasSeenSimulatorToolTip(store),
|
|
108
|
+
// }),
|
|
109
|
+
// );
|
|
110
|
+
|
|
111
|
+
// return (
|
|
112
|
+
// !hasSeenChangesToolTip &&
|
|
113
|
+
// hasSeenSimulatorToolTip
|
|
114
|
+
// );
|
|
115
|
+
|
|
116
|
+
return false;
|
|
117
|
+
};
|
|
@@ -1,25 +1,40 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import {
|
|
2
3
|
Environment as EnvironmentType,
|
|
3
4
|
isUnauthenticatedError,
|
|
4
5
|
isUnauthorizedError,
|
|
5
6
|
} from "@slicemachine/manager/client";
|
|
6
7
|
|
|
7
|
-
import { telemetry } from "@src/apiClient";
|
|
8
|
+
import { getState, telemetry } from "@src/apiClient";
|
|
8
9
|
import { useEnvironments } from "@src/features/environments/useEnvironments";
|
|
9
10
|
import { setEnvironment } from "@src/features/environments/actions/setEnvironment";
|
|
10
11
|
import { useActiveEnvironment } from "@src/features/environments/useActiveEnvironment";
|
|
11
|
-
import { getLegacySliceMachineState } from "@src/features/legacyState/actions/getLegacySliceMachineState";
|
|
12
12
|
import useSliceMachineActions from "@src/modules/useSliceMachineActions";
|
|
13
13
|
import { SideNavEnvironmentSelector } from "@src/components/SideNav";
|
|
14
14
|
import { useNetwork } from "@src/hooks/useNetwork";
|
|
15
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
16
|
+
import { normalizeFrontendSlices } from "@lib/models/common/normalizers/slices";
|
|
17
|
+
import { normalizeFrontendCustomTypes } from "@lib/models/common/normalizers/customType";
|
|
18
|
+
import { getUnSyncedChanges } from "@src/features/sync/getUnSyncChanges";
|
|
19
|
+
import { useAuthStatus } from "@src/hooks/useAuthStatus";
|
|
15
20
|
|
|
16
21
|
export function Environment() {
|
|
17
22
|
const { environments, error: useEnvironmentsError } = useEnvironments();
|
|
18
23
|
const { activeEnvironment } = useActiveEnvironment();
|
|
19
24
|
const { refreshState, openLoginModal } = useSliceMachineActions();
|
|
25
|
+
const { syncChanges } = useAutoSync();
|
|
20
26
|
const isOnline = useNetwork();
|
|
27
|
+
const authStatus = useAuthStatus();
|
|
28
|
+
const [isSwitchingEnv, setIsSwitchingEnv] = useState(false);
|
|
29
|
+
const { autoSyncStatus } = useAutoSync();
|
|
21
30
|
|
|
22
31
|
async function onSelect(environment: EnvironmentType) {
|
|
32
|
+
if (activeEnvironment?.name === environment.name) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setIsSwitchingEnv(true);
|
|
37
|
+
|
|
23
38
|
void telemetry.track({
|
|
24
39
|
event: "environment:switch",
|
|
25
40
|
domain: environment.domain,
|
|
@@ -27,9 +42,39 @@ export function Environment() {
|
|
|
27
42
|
|
|
28
43
|
await setEnvironment(environment);
|
|
29
44
|
|
|
30
|
-
const
|
|
45
|
+
const serverState = await getState();
|
|
46
|
+
refreshState(serverState);
|
|
47
|
+
|
|
48
|
+
const slices = normalizeFrontendSlices(
|
|
49
|
+
serverState.libraries,
|
|
50
|
+
serverState.remoteSlices,
|
|
51
|
+
);
|
|
52
|
+
const customTypes = Object.values(
|
|
53
|
+
normalizeFrontendCustomTypes(
|
|
54
|
+
serverState.customTypes,
|
|
55
|
+
serverState.remoteCustomTypes,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
const { changedCustomTypes, changedSlices } = getUnSyncedChanges({
|
|
59
|
+
authStatus,
|
|
60
|
+
customTypes,
|
|
61
|
+
isOnline,
|
|
62
|
+
libraries: serverState.libraries,
|
|
63
|
+
slices,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
environment.kind === "dev" &&
|
|
68
|
+
(changedCustomTypes.length > 0 || changedSlices.length > 0)
|
|
69
|
+
) {
|
|
70
|
+
syncChanges({
|
|
71
|
+
environment,
|
|
72
|
+
changedCustomTypes,
|
|
73
|
+
changedSlices,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
31
76
|
|
|
32
|
-
|
|
77
|
+
setIsSwitchingEnv(false);
|
|
33
78
|
}
|
|
34
79
|
|
|
35
80
|
if (!isOnline) {
|
|
@@ -42,6 +87,8 @@ export function Environment() {
|
|
|
42
87
|
environments={environments}
|
|
43
88
|
activeEnvironment={activeEnvironment}
|
|
44
89
|
onSelect={onSelect}
|
|
90
|
+
disabled={isSwitchingEnv || autoSyncStatus === "syncing"}
|
|
91
|
+
loading={isSwitchingEnv}
|
|
45
92
|
/>
|
|
46
93
|
);
|
|
47
94
|
}
|
|
@@ -131,10 +131,8 @@ describe("Side Navigation", () => {
|
|
|
131
131
|
test.skip.each([
|
|
132
132
|
["Page types", "/"],
|
|
133
133
|
["Custom types", "/custom-types"],
|
|
134
|
-
["Changes", "/changes"],
|
|
135
|
-
|
|
136
|
-
["Changelog", "/changelog"],
|
|
137
134
|
["Slices", "/slices"],
|
|
135
|
+
["Changelog", "/changelog"],
|
|
138
136
|
// TODO: once we have a plan fo how to display individual libraries change this
|
|
139
137
|
// ["Slices/a", "/slices#slices/a"],
|
|
140
138
|
// ["Slices/b", "/slices#slices/b"]
|
|
@@ -156,95 +154,17 @@ describe("Side Navigation", () => {
|
|
|
156
154
|
},
|
|
157
155
|
);
|
|
158
156
|
|
|
159
|
-
test("should display the number of changes on the '
|
|
157
|
+
test("should display the number of changes on the 'Review changes' item", async () => {
|
|
160
158
|
renderSideNavigation({ canUpdate: true, hasChanges: true });
|
|
161
159
|
|
|
162
|
-
expect(await screen.findByText("
|
|
160
|
+
expect(await screen.findByText("Review changes")).toBeVisible();
|
|
163
161
|
const changesItem = screen
|
|
164
|
-
.getByText("
|
|
165
|
-
.closest("
|
|
162
|
+
.getByText("Review changes")
|
|
163
|
+
.closest("button") as HTMLButtonElement;
|
|
166
164
|
|
|
167
165
|
expect(within(changesItem).getByText("1")).toBeVisible();
|
|
168
166
|
});
|
|
169
167
|
|
|
170
|
-
test("should not display the number of changes or 'Disconnect' on the 'Changes' item", async () => {
|
|
171
|
-
renderSideNavigation({ canUpdate: true, hasChanges: false });
|
|
172
|
-
|
|
173
|
-
expect(await screen.findByText("Changes")).toBeVisible();
|
|
174
|
-
const changesItem = screen
|
|
175
|
-
.getByText("Changes")
|
|
176
|
-
.closest("a") as HTMLAnchorElement;
|
|
177
|
-
|
|
178
|
-
expect(within(changesItem).queryByText("1")).not.toBeInTheDocument();
|
|
179
|
-
expect(
|
|
180
|
-
within(changesItem).queryByText("Logged out"),
|
|
181
|
-
).not.toBeInTheDocument();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("should display the information that user is disconnected on the 'Changes' item when unauthorized", async () => {
|
|
185
|
-
renderSideNavigation({
|
|
186
|
-
canUpdate: true,
|
|
187
|
-
hasChanges: true,
|
|
188
|
-
authStatus: AuthStatus.UNAUTHORIZED,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
expect(await screen.findByText("Changes")).toBeVisible();
|
|
192
|
-
const changesItem = screen
|
|
193
|
-
.getByText("Changes")
|
|
194
|
-
.closest("a") as HTMLAnchorElement;
|
|
195
|
-
|
|
196
|
-
expect(within(changesItem).getByText("Logged out")).toBeVisible();
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("should display the information that user is disconnected on the 'Changes' item when forbidden", async () => {
|
|
200
|
-
renderSideNavigation({
|
|
201
|
-
canUpdate: true,
|
|
202
|
-
hasChanges: true,
|
|
203
|
-
authStatus: AuthStatus.FORBIDDEN,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
expect(await screen.findByText("Changes")).toBeVisible();
|
|
207
|
-
const changesItem = screen
|
|
208
|
-
.getByText("Changes")
|
|
209
|
-
.closest("a") as HTMLAnchorElement;
|
|
210
|
-
|
|
211
|
-
expect(within(changesItem).getByText("Logged out")).toBeVisible();
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test("should display the information that user is disconnected on the 'Changes' item when offline", async () => {
|
|
215
|
-
vi.spyOn(navigator, "onLine", "get").mockReturnValueOnce(false);
|
|
216
|
-
|
|
217
|
-
renderSideNavigation({
|
|
218
|
-
canUpdate: true,
|
|
219
|
-
hasChanges: true,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
expect(await screen.findByText("Changes")).toBeVisible();
|
|
223
|
-
const changesItem = screen
|
|
224
|
-
.getByText("Changes")
|
|
225
|
-
.closest("a") as HTMLAnchorElement;
|
|
226
|
-
|
|
227
|
-
expect(within(changesItem).getByText("Logged out")).toBeVisible();
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("should not display the information that user is disconnected when user is online", async () => {
|
|
231
|
-
vi.spyOn(navigator, "onLine", "get").mockReturnValueOnce(true);
|
|
232
|
-
|
|
233
|
-
renderSideNavigation({
|
|
234
|
-
canUpdate: true,
|
|
235
|
-
hasChanges: true,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
expect(await screen.findByText("Changes")).toBeVisible();
|
|
239
|
-
const changesItem = screen
|
|
240
|
-
.getByText("Changes")
|
|
241
|
-
.closest("a") as HTMLAnchorElement;
|
|
242
|
-
|
|
243
|
-
expect(
|
|
244
|
-
within(changesItem).queryByText("Logged out"),
|
|
245
|
-
).not.toBeInTheDocument();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
168
|
test("Video Item with next", async (ctx) => {
|
|
249
169
|
const adapter = createTestPlugin({
|
|
250
170
|
meta: {
|
|
@@ -11,7 +11,6 @@ import { MathPlusIcon } from "@src/icons/MathPlusIcon";
|
|
|
11
11
|
import { SettingsIcon } from "@src/icons/SettingsIcon";
|
|
12
12
|
import { CUSTOM_TYPES_CONFIG } from "@src/features/customTypes/customTypesConfig";
|
|
13
13
|
import {
|
|
14
|
-
SideNavSeparator,
|
|
15
14
|
SideNavLink,
|
|
16
15
|
SideNavListItem,
|
|
17
16
|
SideNavList,
|
|
@@ -30,7 +29,7 @@ import { CUSTOM_TYPES_MESSAGES } from "@src/features/customTypes/customTypesMess
|
|
|
30
29
|
import { useGitIntegrationExperiment } from "@src/features/settings/git/useGitIntegrationExperiment";
|
|
31
30
|
import { useRepositoryInformation } from "@src/hooks/useRepositoryInformation";
|
|
32
31
|
|
|
33
|
-
import {
|
|
32
|
+
import { ChangesItem } from "./ChangesItem";
|
|
34
33
|
import { Environment } from "./Environment";
|
|
35
34
|
|
|
36
35
|
import * as styles from "./index.css";
|
|
@@ -76,6 +75,8 @@ const Navigation: FC = () => {
|
|
|
76
75
|
href={repositoryUrl}
|
|
77
76
|
/>
|
|
78
77
|
|
|
78
|
+
<ChangesItem />
|
|
79
|
+
|
|
79
80
|
<SideNavList>
|
|
80
81
|
<SideNavListItem>
|
|
81
82
|
<SideNavLink
|
|
@@ -107,12 +108,6 @@ const Navigation: FC = () => {
|
|
|
107
108
|
/>
|
|
108
109
|
</SideNavListItem>
|
|
109
110
|
|
|
110
|
-
<SideNavSeparator />
|
|
111
|
-
|
|
112
|
-
<ChangesListItem />
|
|
113
|
-
|
|
114
|
-
<SideNavSeparator />
|
|
115
|
-
|
|
116
111
|
<SideNavListItem>
|
|
117
112
|
<SideNavLink
|
|
118
113
|
title="Slices"
|