create-interview-cockpit 0.15.0 → 0.16.0
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/package.json +1 -1
- package/template/client/src/App.tsx +3 -0
- package/template/client/src/components/CanvasLabModal.tsx +585 -0
- package/template/client/src/components/LabsPanel.tsx +63 -0
- package/template/client/src/components/Sidebar.tsx +12 -1
- package/template/client/src/components/WorkspaceSwitcher.tsx +36 -0
- package/template/client/src/reactLab.ts +1206 -0
- package/template/client/src/store.ts +24 -1
- package/template/client/src/types.ts +3 -1
- package/template/client/vite.config.ts +15 -8
- package/template/cockpit.json +1 -1
- package/template/server/src/google-drive.ts +4 -1
- package/template/server/src/index.ts +63 -6
- package/template/server/src/storage.ts +1 -0
|
@@ -4,6 +4,11 @@ import { parseInfraLabWorkspace } from "../infraLab";
|
|
|
4
4
|
import {
|
|
5
5
|
parseFrontendLabWorkspace,
|
|
6
6
|
ISOLATED_MODULE_FEDERATION_LAB,
|
|
7
|
+
NEXTJS_MF_PLUGIN_LAB,
|
|
8
|
+
NEXTJS_MF_RUNTIME_LAB,
|
|
9
|
+
NEXTJS_MULTI_ZONES_LAB,
|
|
10
|
+
NEXTJS_MF_RUNTIME_API_LAB,
|
|
11
|
+
RSPACK_SHELL_LAB,
|
|
7
12
|
} from "../reactLab";
|
|
8
13
|
import { BROWSER_SECURITY_TEMPLATES } from "../browserSecurityTemplates";
|
|
9
14
|
import type { ContextFile } from "../types";
|
|
@@ -23,6 +28,7 @@ import {
|
|
|
23
28
|
Link2Off,
|
|
24
29
|
Network,
|
|
25
30
|
Shield,
|
|
31
|
+
PenLine,
|
|
26
32
|
} from "lucide-react";
|
|
27
33
|
|
|
28
34
|
// ─── Helpers ─────────────────────────────────────────────
|
|
@@ -34,6 +40,7 @@ const LAB_ORIGINS = new Set([
|
|
|
34
40
|
"react",
|
|
35
41
|
"nextjs",
|
|
36
42
|
"module-federation",
|
|
43
|
+
"canvas",
|
|
37
44
|
]);
|
|
38
45
|
|
|
39
46
|
function isLabFile(cf: ContextFile) {
|
|
@@ -193,6 +200,7 @@ export default function LabsPanel() {
|
|
|
193
200
|
openNextLab,
|
|
194
201
|
openModuleFederationLab,
|
|
195
202
|
openDeploymentLab,
|
|
203
|
+
openCanvasLab,
|
|
196
204
|
removeQuestionFile,
|
|
197
205
|
detachLabFile,
|
|
198
206
|
attachLabFile,
|
|
@@ -406,6 +414,17 @@ export default function LabsPanel() {
|
|
|
406
414
|
}
|
|
407
415
|
};
|
|
408
416
|
|
|
417
|
+
const openCanvasFile = async (cf: ContextFile) => {
|
|
418
|
+
try {
|
|
419
|
+
const raw = await fetch(`/api/context-files/${cf.id}/content`)
|
|
420
|
+
.then((r) => r.json())
|
|
421
|
+
.then((d) => d.content as string);
|
|
422
|
+
openCanvasLab(raw, cf.id);
|
|
423
|
+
} catch {
|
|
424
|
+
/* ignore */
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
409
428
|
// ── Section renderer ─────────────────────────────────────
|
|
410
429
|
|
|
411
430
|
function Section({
|
|
@@ -640,12 +659,56 @@ export default function LabsPanel() {
|
|
|
640
659
|
onClick: () =>
|
|
641
660
|
openModuleFederationLab(ISOLATED_MODULE_FEDERATION_LAB),
|
|
642
661
|
},
|
|
662
|
+
{
|
|
663
|
+
label: "Next.js MF — Plugin (Option A)",
|
|
664
|
+
description:
|
|
665
|
+
"Next.js shell + remote via built-in webpack.container.ModuleFederationPlugin",
|
|
666
|
+
onClick: () => openModuleFederationLab(NEXTJS_MF_PLUGIN_LAB),
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
label: "Next.js MF — Runtime Loader (Option B)",
|
|
670
|
+
description:
|
|
671
|
+
"Next.js shell with no plugin — loads remote via plain script injection",
|
|
672
|
+
onClick: () => openModuleFederationLab(NEXTJS_MF_RUNTIME_LAB),
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
label: "Next.js — Multi-Zones",
|
|
676
|
+
description:
|
|
677
|
+
"Two independent Next.js apps split by URL path via rewrites — Vercel recommended",
|
|
678
|
+
onClick: () => openModuleFederationLab(NEXTJS_MULTI_ZONES_LAB),
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
label: "Next.js — MF Runtime API",
|
|
682
|
+
description:
|
|
683
|
+
"@module-federation/enhanced/runtime inside a 'use client' component — no webpack config",
|
|
684
|
+
onClick: () =>
|
|
685
|
+
openModuleFederationLab(NEXTJS_MF_RUNTIME_API_LAB),
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
label: "Rspack Shell — Native MF 2.0",
|
|
689
|
+
description:
|
|
690
|
+
"Rspack as the host with built-in MF support; webpack app as the remote",
|
|
691
|
+
onClick: () => openModuleFederationLab(RSPACK_SHELL_LAB),
|
|
692
|
+
},
|
|
643
693
|
]}
|
|
644
694
|
onOpen={openMFFile}
|
|
645
695
|
openTitle="Open in Webpack Module Federation Lab"
|
|
646
696
|
accentClass="text-emerald-200"
|
|
647
697
|
bgClass="bg-emerald-500/10 border border-emerald-500/20"
|
|
648
698
|
/>
|
|
699
|
+
<Section
|
|
700
|
+
title="Canvas Labs"
|
|
701
|
+
icon={PenLine}
|
|
702
|
+
iconColor="text-orange-400/70"
|
|
703
|
+
origin="canvas"
|
|
704
|
+
emptyText="Save a canvas lab to reopen it here"
|
|
705
|
+
onNewLab={() => openCanvasLab()}
|
|
706
|
+
newLabTitle="Open Canvas Lab"
|
|
707
|
+
onOpen={openCanvasFile}
|
|
708
|
+
openTitle="Open in Canvas Lab"
|
|
709
|
+
accentClass="text-orange-200"
|
|
710
|
+
bgClass="bg-orange-500/10 border border-orange-500/20"
|
|
711
|
+
/>
|
|
649
712
|
</div>
|
|
650
713
|
) : (
|
|
651
714
|
<div className="flex-1 flex items-center justify-center">
|
|
@@ -154,6 +154,9 @@ export default function Sidebar() {
|
|
|
154
154
|
|
|
155
155
|
// Drive subfolder navigator
|
|
156
156
|
const activeWs = workspaces.find((w) => w.id === activeWorkspaceId);
|
|
157
|
+
const activeWsSortOrder = (activeWs?.questionSortOrder ?? "name") as
|
|
158
|
+
| "name"
|
|
159
|
+
| "createdAt";
|
|
157
160
|
const isDriveWs =
|
|
158
161
|
activeWs?.type === "google_drive" && !!activeWs.driveConfig?.folderId;
|
|
159
162
|
const currentSubFolder = activeWs?.driveConfig?.subFolderId
|
|
@@ -804,7 +807,15 @@ export default function Sidebar() {
|
|
|
804
807
|
)
|
|
805
808
|
.map((topic) => {
|
|
806
809
|
const isExpanded = expandedTopics.includes(topic.id);
|
|
807
|
-
const questions = questionsByTopic[topic.id] || []
|
|
810
|
+
const questions = [...(questionsByTopic[topic.id] || [])].sort(
|
|
811
|
+
(a, b) =>
|
|
812
|
+
activeWsSortOrder === "createdAt"
|
|
813
|
+
? a.createdAt.localeCompare(b.createdAt)
|
|
814
|
+
: a.title.localeCompare(b.title, undefined, {
|
|
815
|
+
numeric: true,
|
|
816
|
+
sensitivity: "base",
|
|
817
|
+
}),
|
|
818
|
+
);
|
|
808
819
|
|
|
809
820
|
return (
|
|
810
821
|
<div key={topic.id}>
|
|
@@ -27,6 +27,7 @@ export default function WorkspaceSwitcher() {
|
|
|
27
27
|
createWorkspace,
|
|
28
28
|
deleteWorkspace,
|
|
29
29
|
renameWorkspace,
|
|
30
|
+
patchWorkspace,
|
|
30
31
|
syncWorkspace,
|
|
31
32
|
linkDriveFolder,
|
|
32
33
|
attachDriveFolder,
|
|
@@ -814,6 +815,41 @@ export default function WorkspaceSwitcher() {
|
|
|
814
815
|
)}
|
|
815
816
|
</div>
|
|
816
817
|
)}
|
|
818
|
+
|
|
819
|
+
{/* Question sort order */}
|
|
820
|
+
<div
|
|
821
|
+
className="mt-1 ml-5 flex items-center gap-1"
|
|
822
|
+
onClick={(e) => e.stopPropagation()}
|
|
823
|
+
>
|
|
824
|
+
<span className="text-[10px] text-slate-600">Order:</span>
|
|
825
|
+
<button
|
|
826
|
+
onClick={() =>
|
|
827
|
+
patchWorkspace(ws.id, { questionSortOrder: "name" })
|
|
828
|
+
}
|
|
829
|
+
className={`text-[10px] px-1 rounded transition-colors ${
|
|
830
|
+
(ws.questionSortOrder ?? "name") === "name"
|
|
831
|
+
? "text-cyan-400"
|
|
832
|
+
: "text-slate-600 hover:text-slate-400"
|
|
833
|
+
}`}
|
|
834
|
+
>
|
|
835
|
+
Name
|
|
836
|
+
</button>
|
|
837
|
+
<span className="text-[10px] text-slate-700">·</span>
|
|
838
|
+
<button
|
|
839
|
+
onClick={() =>
|
|
840
|
+
patchWorkspace(ws.id, {
|
|
841
|
+
questionSortOrder: "createdAt",
|
|
842
|
+
})
|
|
843
|
+
}
|
|
844
|
+
className={`text-[10px] px-1 rounded transition-colors ${
|
|
845
|
+
ws.questionSortOrder === "createdAt"
|
|
846
|
+
? "text-cyan-400"
|
|
847
|
+
: "text-slate-600 hover:text-slate-400"
|
|
848
|
+
}`}
|
|
849
|
+
>
|
|
850
|
+
Date created
|
|
851
|
+
</button>
|
|
852
|
+
</div>
|
|
817
853
|
</div>
|
|
818
854
|
))}
|
|
819
855
|
</div>
|