@drawnagency/primitives 0.1.15 → 0.1.17
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/dist/components/shell/BuildStatusIndicator.d.ts +1 -3
- package/dist/components/shell/BuildStatusIndicator.d.ts.map +1 -1
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/hooks/useBuildStatus.d.ts +0 -2
- package/dist/hooks/useBuildStatus.d.ts.map +1 -1
- package/dist/hooks/useEditorPublish.d.ts +2 -1
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/shell/BuildStatusIndicator.tsx +30 -38
- package/src/components/shell/EditorShell.tsx +53 -38
- package/src/hooks/useBuildStatus.ts +1 -30
- package/src/hooks/useEditorPublish.ts +13 -11
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
interface BuildStatusIndicatorProps {
|
|
2
2
|
state: "idle" | "building" | "ready" | "error";
|
|
3
3
|
deployUrl: string | null;
|
|
4
|
-
visible: boolean;
|
|
5
|
-
onDismiss: () => void;
|
|
6
4
|
}
|
|
7
|
-
export declare function BuildStatusIndicator({ state, deployUrl,
|
|
5
|
+
export declare function BuildStatusIndicator({ state, deployUrl, }: BuildStatusIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
8
6
|
export {};
|
|
9
7
|
//# sourceMappingURL=BuildStatusIndicator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BuildStatusIndicator.d.ts","sourceRoot":"","sources":["../../../src/components/shell/BuildStatusIndicator.tsx"],"names":[],"mappings":"AAGA,UAAU,yBAAyB;IACjC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"BuildStatusIndicator.d.ts","sourceRoot":"","sources":["../../../src/components/shell/BuildStatusIndicator.tsx"],"names":[],"mappings":"AAGA,UAAU,yBAAyB;IACjC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AA6BD,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,SAAS,GACV,EAAE,yBAAyB,kDAoB3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAYjD,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAYjD,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAcxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CAskBP"}
|
|
@@ -2,8 +2,6 @@ type BuildState = "idle" | "building" | "ready" | "error";
|
|
|
2
2
|
interface BuildStatusResult {
|
|
3
3
|
state: BuildState;
|
|
4
4
|
deployUrl: string | null;
|
|
5
|
-
visible: boolean;
|
|
6
|
-
dismiss: () => void;
|
|
7
5
|
startTracking: () => void;
|
|
8
6
|
}
|
|
9
7
|
export declare function useBuildStatus(): BuildStatusResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuildStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuildStatus.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAS1D,UAAU,iBAAiB;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,
|
|
1
|
+
{"version":3,"file":"useBuildStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuildStatus.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAS1D,UAAU,iBAAiB;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAID,wBAAgB,cAAc,IAAI,iBAAiB,CA0FlD"}
|
|
@@ -18,8 +18,9 @@ interface PublishDeps {
|
|
|
18
18
|
onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
|
|
19
19
|
onPublishComplete?: () => void;
|
|
20
20
|
}
|
|
21
|
+
export type PublishAction = "idle" | "saving" | "publishing";
|
|
21
22
|
export declare function useEditorPublish({ flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, onPublishComplete, }: PublishDeps): {
|
|
22
|
-
|
|
23
|
+
publishAction: PublishAction;
|
|
23
24
|
publishFeedback: string | null;
|
|
24
25
|
handleSave: () => Promise<void>;
|
|
25
26
|
handlePublish: () => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAe/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AASD,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE,WAAW;;;;;;EAuSb"}
|
|
1
|
+
{"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAe/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AASD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE,WAAW;;;;;;EAuSb"}
|
package/package.json
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
3
|
|
|
4
4
|
interface BuildStatusIndicatorProps {
|
|
5
5
|
state: "idle" | "building" | "ready" | "error";
|
|
6
6
|
deployUrl: string | null;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function LoadingEllipsis() {
|
|
10
|
+
const [dots, setDots] = useState(0);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const id = setInterval(() => setDots((d) => (d + 1) % 4), 400);
|
|
13
|
+
return () => clearInterval(id);
|
|
14
|
+
}, []);
|
|
15
|
+
return <>{".".repeat(dots)}</>;
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
const stateConfig = {
|
|
12
19
|
building: {
|
|
13
|
-
label: "
|
|
14
|
-
className: "border-orange-500 text-orange-600
|
|
20
|
+
label: "Publishing",
|
|
21
|
+
className: "border-orange-500 text-orange-600",
|
|
15
22
|
dotClassName: "bg-orange-500 animate-pulse",
|
|
16
23
|
},
|
|
17
24
|
ready: {
|
|
18
|
-
label: "
|
|
19
|
-
className: "border-green-600 text-green-600
|
|
25
|
+
label: "Published",
|
|
26
|
+
className: "border-green-600 text-green-600",
|
|
20
27
|
dotClassName: "bg-green-600",
|
|
21
28
|
},
|
|
22
29
|
error: {
|
|
23
|
-
label: "
|
|
24
|
-
className: "border-red-600 text-red-600
|
|
30
|
+
label: "Publish failed",
|
|
31
|
+
className: "border-red-600 text-red-600",
|
|
25
32
|
dotClassName: "bg-red-600",
|
|
26
33
|
},
|
|
27
34
|
} as const;
|
|
@@ -29,39 +36,24 @@ const stateConfig = {
|
|
|
29
36
|
export function BuildStatusIndicator({
|
|
30
37
|
state,
|
|
31
38
|
deployUrl,
|
|
32
|
-
visible,
|
|
33
|
-
onDismiss,
|
|
34
39
|
}: BuildStatusIndicatorProps) {
|
|
35
|
-
if (
|
|
40
|
+
if (state === "idle") return null;
|
|
36
41
|
|
|
37
42
|
const config = stateConfig[state];
|
|
38
43
|
|
|
39
44
|
return (
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</a>
|
|
54
|
-
<button
|
|
55
|
-
type="button"
|
|
56
|
-
onClick={onDismiss}
|
|
57
|
-
aria-label="Dismiss build status"
|
|
58
|
-
className={cn(
|
|
59
|
-
"inline-flex items-center rounded-r border border-l-0 px-1.5 py-1.5 transition-colors cursor-pointer",
|
|
60
|
-
config.className,
|
|
61
|
-
)}
|
|
62
|
-
>
|
|
63
|
-
<X size={12} />
|
|
64
|
-
</button>
|
|
65
|
-
</span>
|
|
45
|
+
<a
|
|
46
|
+
href={deployUrl ?? "#"}
|
|
47
|
+
target="_blank"
|
|
48
|
+
rel="noopener noreferrer"
|
|
49
|
+
aria-label={config.label}
|
|
50
|
+
className={cn(
|
|
51
|
+
"inline-flex items-center gap-1.5 rounded px-3 py-1.5 text-xs font-medium border transition-colors",
|
|
52
|
+
config.className,
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
<span className={cn("h-1.5 w-1.5 rounded-full", config.dotClassName)} />
|
|
56
|
+
{config.label}{state === "building" && <LoadingEllipsis />}
|
|
57
|
+
</a>
|
|
66
58
|
);
|
|
67
59
|
}
|
|
@@ -37,7 +37,6 @@ import { useEditorPersistence } from "../../hooks/useEditorPersistence";
|
|
|
37
37
|
import { useEditorPublish } from "../../hooks/useEditorPublish";
|
|
38
38
|
import { useContentLifecycle } from "../../hooks/useContentLifecycle";
|
|
39
39
|
import { useBuildStatus } from "../../hooks/useBuildStatus";
|
|
40
|
-
import { BuildStatusIndicator } from "./BuildStatusIndicator";
|
|
41
40
|
import { useMediaPipeline } from "../../hooks/useMediaPipeline";
|
|
42
41
|
import { formatTimestamp } from "../../lib/timestamp";
|
|
43
42
|
import { generateNavLinks } from "../../lib/nav";
|
|
@@ -127,7 +126,7 @@ export default function EditorShell({
|
|
|
127
126
|
|
|
128
127
|
const buildStatus = useBuildStatus();
|
|
129
128
|
|
|
130
|
-
const {
|
|
129
|
+
const { publishAction, publishFeedback, handleSave, handlePublish, handleSaveAndPublish } = useEditorPublish({
|
|
131
130
|
flushNow: persistence.flushNow,
|
|
132
131
|
cancelPendingFlush: persistence.cancelPendingFlush,
|
|
133
132
|
isConfigDirty: persistence.isConfigDirty,
|
|
@@ -574,7 +573,7 @@ export default function EditorShell({
|
|
|
574
573
|
<EditorToolbar
|
|
575
574
|
buttonState={buttonState}
|
|
576
575
|
localChangesExist={localChangesExist}
|
|
577
|
-
|
|
576
|
+
publishAction={publishAction}
|
|
578
577
|
publishFeedback={publishFeedback}
|
|
579
578
|
onSave={handleSave}
|
|
580
579
|
onPublish={handlePublish}
|
|
@@ -587,9 +586,6 @@ export default function EditorShell({
|
|
|
587
586
|
}}
|
|
588
587
|
processingItems={mediaPipeline.processingItems}
|
|
589
588
|
buildState={buildStatus.state}
|
|
590
|
-
buildDeployUrl={buildStatus.deployUrl}
|
|
591
|
-
buildVisible={buildStatus.visible}
|
|
592
|
-
onBuildDismiss={buildStatus.dismiss}
|
|
593
589
|
/>
|
|
594
590
|
|
|
595
591
|
<EditorContent
|
|
@@ -597,7 +593,7 @@ export default function EditorShell({
|
|
|
597
593
|
audiences={audiences}
|
|
598
594
|
dirtySectionIds={dirtySectionIds}
|
|
599
595
|
deletedSections={deletedSections}
|
|
600
|
-
isPublishing={
|
|
596
|
+
isPublishing={publishAction !== "idle"}
|
|
601
597
|
onSectionChange={onSectionChange}
|
|
602
598
|
onAddSection={onAddSection}
|
|
603
599
|
onDeleteSection={onDeleteSection}
|
|
@@ -876,10 +872,47 @@ function GlobalModal() {
|
|
|
876
872
|
);
|
|
877
873
|
}
|
|
878
874
|
|
|
875
|
+
function StatusText({
|
|
876
|
+
publishAction,
|
|
877
|
+
publishFeedback,
|
|
878
|
+
buildState,
|
|
879
|
+
}: {
|
|
880
|
+
publishAction: "idle" | "saving" | "publishing";
|
|
881
|
+
publishFeedback: string | null;
|
|
882
|
+
buildState: "idle" | "building" | "ready" | "error";
|
|
883
|
+
}) {
|
|
884
|
+
if (publishAction === "saving") {
|
|
885
|
+
return <span className="text-xs font-medium text-base-content/60">Saving...</span>;
|
|
886
|
+
}
|
|
887
|
+
if (publishAction === "publishing") {
|
|
888
|
+
return <span className="text-xs font-medium text-base-content/60">Publishing...</span>;
|
|
889
|
+
}
|
|
890
|
+
if (buildState === "building") {
|
|
891
|
+
return <span className="text-xs font-medium text-orange-600">Publishing...</span>;
|
|
892
|
+
}
|
|
893
|
+
if (buildState === "ready") {
|
|
894
|
+
return <span className="text-xs font-medium text-green-600">Published</span>;
|
|
895
|
+
}
|
|
896
|
+
if (buildState === "error") {
|
|
897
|
+
return <span className="text-xs font-medium text-red-600">Publish failed</span>;
|
|
898
|
+
}
|
|
899
|
+
if (publishFeedback) {
|
|
900
|
+
return (
|
|
901
|
+
<span className={cn(
|
|
902
|
+
"text-xs font-medium",
|
|
903
|
+
publishFeedback === "Saved" ? "text-green-600" : "text-red-600",
|
|
904
|
+
)}>
|
|
905
|
+
{publishFeedback}
|
|
906
|
+
</span>
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
|
|
879
912
|
function EditorToolbar({
|
|
880
913
|
buttonState,
|
|
881
914
|
localChangesExist,
|
|
882
|
-
|
|
915
|
+
publishAction,
|
|
883
916
|
publishFeedback,
|
|
884
917
|
onSave,
|
|
885
918
|
onPublish,
|
|
@@ -889,13 +922,10 @@ function EditorToolbar({
|
|
|
889
922
|
onMediaClick,
|
|
890
923
|
processingItems,
|
|
891
924
|
buildState,
|
|
892
|
-
buildDeployUrl,
|
|
893
|
-
buildVisible,
|
|
894
|
-
onBuildDismiss,
|
|
895
925
|
}: {
|
|
896
926
|
buttonState: "synced" | "publish" | "saveAndPublish";
|
|
897
927
|
localChangesExist: boolean;
|
|
898
|
-
|
|
928
|
+
publishAction: "idle" | "saving" | "publishing";
|
|
899
929
|
publishFeedback: string | null;
|
|
900
930
|
onSave: () => void;
|
|
901
931
|
onPublish: () => void;
|
|
@@ -905,33 +935,19 @@ function EditorToolbar({
|
|
|
905
935
|
onMediaClick: () => void;
|
|
906
936
|
processingItems: QueueItem[];
|
|
907
937
|
buildState: "idle" | "building" | "ready" | "error";
|
|
908
|
-
buildDeployUrl: string | null;
|
|
909
|
-
buildVisible: boolean;
|
|
910
|
-
onBuildDismiss: () => void;
|
|
911
938
|
}) {
|
|
912
939
|
const { isEditMode, viewBranch, setViewBranch, toggleEditMode } = useEditorContext();
|
|
913
940
|
|
|
914
941
|
return (
|
|
915
942
|
<>
|
|
916
943
|
{isEditMode && (
|
|
917
|
-
<div className="fixed top-0 right-0 left-0 z-50
|
|
944
|
+
<div className="fixed top-0 right-0 left-0 z-50 grid grid-cols-3 items-center border-b border-base-200 bg-base px-4 py-2">
|
|
918
945
|
<div className="flex items-center gap-2">
|
|
919
|
-
{publishFeedback && (
|
|
920
|
-
<span className={cn(
|
|
921
|
-
"text-xs font-medium",
|
|
922
|
-
publishFeedback === "Published" || publishFeedback === "Saved"
|
|
923
|
-
? "text-green-600"
|
|
924
|
-
: "text-red-600",
|
|
925
|
-
)}>
|
|
926
|
-
{publishFeedback}
|
|
927
|
-
</span>
|
|
928
|
-
)}
|
|
929
946
|
{buttonState === "saveAndPublish" && (
|
|
930
947
|
<SplitButton
|
|
931
948
|
label="Save & Publish"
|
|
932
949
|
onClick={onSaveAndPublish}
|
|
933
|
-
|
|
934
|
-
loadingLabel="Publishing..."
|
|
950
|
+
disabled={publishAction !== "idle"}
|
|
935
951
|
options={[{ label: "Save", onClick: onSave }]}
|
|
936
952
|
/>
|
|
937
953
|
)}
|
|
@@ -939,8 +955,7 @@ function EditorToolbar({
|
|
|
939
955
|
<SplitButton
|
|
940
956
|
label="Publish"
|
|
941
957
|
onClick={onPublish}
|
|
942
|
-
|
|
943
|
-
loadingLabel="Publishing..."
|
|
958
|
+
disabled={publishAction !== "idle"}
|
|
944
959
|
options={[]}
|
|
945
960
|
/>
|
|
946
961
|
)}
|
|
@@ -952,24 +967,24 @@ function EditorToolbar({
|
|
|
952
967
|
options={[]}
|
|
953
968
|
/>
|
|
954
969
|
)}
|
|
955
|
-
{localChangesExist &&
|
|
970
|
+
{localChangesExist && publishAction === "idle" && (
|
|
956
971
|
<Button
|
|
957
972
|
type="button"
|
|
958
973
|
variant="destructive"
|
|
959
974
|
onClick={onDiscardClick}
|
|
960
|
-
disabled={isPublishing}
|
|
961
975
|
>
|
|
962
976
|
Discard Changes
|
|
963
977
|
</Button>
|
|
964
978
|
)}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
979
|
+
</div>
|
|
980
|
+
<div className="flex items-center justify-center">
|
|
981
|
+
<StatusText
|
|
982
|
+
publishAction={publishAction}
|
|
983
|
+
publishFeedback={publishFeedback}
|
|
984
|
+
buildState={buildState}
|
|
970
985
|
/>
|
|
971
986
|
</div>
|
|
972
|
-
<div className="flex items-center gap-2">
|
|
987
|
+
<div className="flex items-center justify-end gap-2">
|
|
973
988
|
<ProcessingIndicator items={processingItems} />
|
|
974
989
|
<IconButton
|
|
975
990
|
icon={<ImageIcon size={16} />}
|
|
@@ -12,20 +12,15 @@ interface BuildStatusResponse {
|
|
|
12
12
|
interface BuildStatusResult {
|
|
13
13
|
state: BuildState;
|
|
14
14
|
deployUrl: string | null;
|
|
15
|
-
visible: boolean;
|
|
16
|
-
dismiss: () => void;
|
|
17
15
|
startTracking: () => void;
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
const POLL_INTERVAL = 5000;
|
|
21
|
-
const AUTO_DISMISS_DELAY = 10000;
|
|
22
19
|
|
|
23
20
|
export function useBuildStatus(): BuildStatusResult {
|
|
24
21
|
const [state, setState] = useState<BuildState>("idle");
|
|
25
22
|
const [deployUrl, setDeployUrl] = useState<string | null>(null);
|
|
26
|
-
const [dismissed, setDismissed] = useState(false);
|
|
27
23
|
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
28
|
-
const dismissRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
29
24
|
const isPolling = useRef(false);
|
|
30
25
|
|
|
31
26
|
const stopPolling = useCallback(() => {
|
|
@@ -65,16 +60,9 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
65
60
|
|
|
66
61
|
setState(data.state);
|
|
67
62
|
setDeployUrl(data.deployUrl);
|
|
68
|
-
setDismissed(false);
|
|
69
63
|
|
|
70
64
|
if (data.state === "ready" || data.state === "error") {
|
|
71
65
|
stopPolling();
|
|
72
|
-
|
|
73
|
-
if (data.state === "ready") {
|
|
74
|
-
dismissRef.current = setTimeout(() => {
|
|
75
|
-
setDismissed(true);
|
|
76
|
-
}, AUTO_DISMISS_DELAY);
|
|
77
|
-
}
|
|
78
66
|
}
|
|
79
67
|
},
|
|
80
68
|
[stopPolling],
|
|
@@ -109,31 +97,14 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
109
97
|
return () => {
|
|
110
98
|
cancelled = true;
|
|
111
99
|
stopPolling();
|
|
112
|
-
if (dismissRef.current) clearTimeout(dismissRef.current);
|
|
113
100
|
};
|
|
114
101
|
}, [fetchStatus, handleStatusUpdate, startPolling, stopPolling]);
|
|
115
102
|
|
|
116
|
-
const dismiss = useCallback(() => {
|
|
117
|
-
setDismissed(true);
|
|
118
|
-
stopPolling();
|
|
119
|
-
if (dismissRef.current) {
|
|
120
|
-
clearTimeout(dismissRef.current);
|
|
121
|
-
dismissRef.current = null;
|
|
122
|
-
}
|
|
123
|
-
}, [stopPolling]);
|
|
124
|
-
|
|
125
103
|
const startTracking = useCallback(() => {
|
|
126
104
|
setState("building");
|
|
127
105
|
setDeployUrl(null);
|
|
128
|
-
setDismissed(false);
|
|
129
|
-
if (dismissRef.current) {
|
|
130
|
-
clearTimeout(dismissRef.current);
|
|
131
|
-
dismissRef.current = null;
|
|
132
|
-
}
|
|
133
106
|
startPolling();
|
|
134
107
|
}, [startPolling]);
|
|
135
108
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return { state, deployUrl, visible, dismiss, startTracking };
|
|
109
|
+
return { state, deployUrl, startTracking };
|
|
139
110
|
}
|
|
@@ -41,6 +41,8 @@ interface GatheredMedia {
|
|
|
41
41
|
hasMediaChanges: boolean;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export type PublishAction = "idle" | "saving" | "publishing";
|
|
45
|
+
|
|
44
46
|
export function useEditorPublish({
|
|
45
47
|
flushNow,
|
|
46
48
|
cancelPendingFlush,
|
|
@@ -58,7 +60,7 @@ export function useEditorPublish({
|
|
|
58
60
|
onShasUpdated,
|
|
59
61
|
onPublishComplete,
|
|
60
62
|
}: PublishDeps) {
|
|
61
|
-
const [
|
|
63
|
+
const [publishAction, setPublishAction] = useState<PublishAction>("idle");
|
|
62
64
|
const [publishFeedback, setPublishFeedback] = useState<string | null>(null);
|
|
63
65
|
const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
64
66
|
|
|
@@ -136,7 +138,7 @@ export function useEditorPublish({
|
|
|
136
138
|
const handleSave = useCallback(async () => {
|
|
137
139
|
if (!siteConfig) return;
|
|
138
140
|
|
|
139
|
-
|
|
141
|
+
setPublishAction("saving");
|
|
140
142
|
setPublishFeedback(null);
|
|
141
143
|
|
|
142
144
|
try {
|
|
@@ -147,7 +149,7 @@ export function useEditorPublish({
|
|
|
147
149
|
const hasMediaChanges = pendingMediaItems.length > 0 || pendingMediaDeletions.length > 0;
|
|
148
150
|
const hasDeletedSections = (deletedSectionIds?.length ?? 0) > 0;
|
|
149
151
|
if (!hasChanges && !isConfigDirty() && !hasMediaChanges && !hasDeletedSections) {
|
|
150
|
-
|
|
152
|
+
setPublishAction("idle");
|
|
151
153
|
return;
|
|
152
154
|
}
|
|
153
155
|
|
|
@@ -216,12 +218,12 @@ export function useEditorPublish({
|
|
|
216
218
|
console.error("Save failed:", error);
|
|
217
219
|
showFeedback("Save failed", 5000);
|
|
218
220
|
} finally {
|
|
219
|
-
|
|
221
|
+
setPublishAction("idle");
|
|
220
222
|
}
|
|
221
223
|
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback]);
|
|
222
224
|
|
|
223
225
|
const handlePublish = useCallback(async () => {
|
|
224
|
-
|
|
226
|
+
setPublishAction("publishing");
|
|
225
227
|
setPublishFeedback(null);
|
|
226
228
|
|
|
227
229
|
try {
|
|
@@ -238,20 +240,19 @@ export function useEditorPublish({
|
|
|
238
240
|
const { sha } = await response.json();
|
|
239
241
|
|
|
240
242
|
onShasUpdated(null, sha);
|
|
241
|
-
showFeedback("Published", 3000);
|
|
242
243
|
onPublishComplete?.();
|
|
243
244
|
} catch (error) {
|
|
244
245
|
console.error("Publish failed:", error);
|
|
245
246
|
showFeedback("Publish failed", 5000);
|
|
246
247
|
} finally {
|
|
247
|
-
|
|
248
|
+
setPublishAction("idle");
|
|
248
249
|
}
|
|
249
250
|
}, [onShasUpdated, showFeedback, onPublishComplete]);
|
|
250
251
|
|
|
251
252
|
const handleSaveAndPublish = useCallback(async () => {
|
|
252
253
|
if (!siteConfig) return;
|
|
253
254
|
|
|
254
|
-
|
|
255
|
+
setPublishAction("saving");
|
|
255
256
|
setPublishFeedback(null);
|
|
256
257
|
|
|
257
258
|
try {
|
|
@@ -316,6 +317,8 @@ export function useEditorPublish({
|
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
|
|
320
|
+
setPublishAction("publishing");
|
|
321
|
+
|
|
319
322
|
const publishResponse = await fetch("/api/publish", {
|
|
320
323
|
method: "POST",
|
|
321
324
|
headers: { "Content-Type": "application/json" },
|
|
@@ -341,15 +344,14 @@ export function useEditorPublish({
|
|
|
341
344
|
}
|
|
342
345
|
|
|
343
346
|
onShasUpdated(null, sha);
|
|
344
|
-
showFeedback("Published", 3000);
|
|
345
347
|
onPublishComplete?.();
|
|
346
348
|
} catch (error) {
|
|
347
349
|
console.error("Publish failed:", error);
|
|
348
350
|
showFeedback("Publish failed", 5000);
|
|
349
351
|
} finally {
|
|
350
|
-
|
|
352
|
+
setPublishAction("idle");
|
|
351
353
|
}
|
|
352
354
|
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete]);
|
|
353
355
|
|
|
354
|
-
return {
|
|
356
|
+
return { publishAction, publishFeedback, handleSave, handlePublish, handleSaveAndPublish };
|
|
355
357
|
}
|