create-interview-cockpit 0.23.1 → 0.23.2
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
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, Fragment } from "react";
|
|
2
2
|
import { useStore } from "../store";
|
|
3
3
|
import type { Question } from "../types";
|
|
4
|
+
import type { DriveFolder } from "../api";
|
|
4
5
|
import * as api from "../api";
|
|
5
6
|
import FileAttachments from "./FileAttachments";
|
|
6
7
|
import WorkspaceSwitcher from "./WorkspaceSwitcher";
|
|
@@ -116,6 +117,8 @@ export default function Sidebar() {
|
|
|
116
117
|
driveRootFolders,
|
|
117
118
|
loadingDriveFolders,
|
|
118
119
|
fetchDriveRootFolders,
|
|
120
|
+
fetchDriveSubfolders,
|
|
121
|
+
createDriveSubfolder,
|
|
119
122
|
selectDriveSubfolder,
|
|
120
123
|
clearDriveSubfolder,
|
|
121
124
|
syncWorkspace,
|
|
@@ -214,6 +217,10 @@ export default function Sidebar() {
|
|
|
214
217
|
const isAtDriveFolderRoot = isDriveWs && !currentSubFolder;
|
|
215
218
|
const canSyncDriveFolder =
|
|
216
219
|
!!activeWs?.driveConfig?.folderId && !isAtDriveFolderRoot;
|
|
220
|
+
const canPushLocalDrive =
|
|
221
|
+
activeWs?.type === "local" && !!activeWs.driveConfig?.folderId;
|
|
222
|
+
const canPushDriveDestination =
|
|
223
|
+
!!activeWs?.driveConfig?.folderId && (!isDriveWs || !!currentSubFolder);
|
|
217
224
|
const driveSyncTargetName =
|
|
218
225
|
currentSubFolder?.name ?? activeWs?.driveConfig?.folderName ?? "Drive";
|
|
219
226
|
const [navigating, setNavigating] = useState(false);
|
|
@@ -224,6 +231,15 @@ export default function Sidebar() {
|
|
|
224
231
|
const [topicDriveStatus, setTopicDriveStatus] = useState<
|
|
225
232
|
Record<string, string>
|
|
226
233
|
>({});
|
|
234
|
+
const [pushPicker, setPushPicker] = useState<{
|
|
235
|
+
mode: "workspace" | "topic";
|
|
236
|
+
topicId?: string;
|
|
237
|
+
folders: DriveFolder[];
|
|
238
|
+
loading: boolean;
|
|
239
|
+
} | null>(null);
|
|
240
|
+
const [pushPickerNewFolderName, setPushPickerNewFolderName] = useState("");
|
|
241
|
+
const [pushPickerCreatingFolder, setPushPickerCreatingFolder] =
|
|
242
|
+
useState(false);
|
|
227
243
|
const [wsFilesExpanded, setWsFilesExpanded] = useState(true);
|
|
228
244
|
const [driveFileSyncStatus, setDriveFileSyncStatus] = useState<string | null>(
|
|
229
245
|
null,
|
|
@@ -277,15 +293,12 @@ export default function Sidebar() {
|
|
|
277
293
|
}
|
|
278
294
|
};
|
|
279
295
|
|
|
280
|
-
const
|
|
296
|
+
const pushWorkspaceToDrive = async (targetFolderId: string) => {
|
|
281
297
|
if (!activeWorkspaceId || !activeWs?.driveConfig?.folderId) return;
|
|
282
298
|
setPushing(true);
|
|
283
299
|
setDriveFileSyncStatus(null);
|
|
284
300
|
try {
|
|
285
|
-
const result = await exportWorkspace(
|
|
286
|
-
activeWorkspaceId,
|
|
287
|
-
activeWs.driveConfig.subFolderId,
|
|
288
|
-
);
|
|
301
|
+
const result = await exportWorkspace(activeWorkspaceId, targetFolderId);
|
|
289
302
|
if ("needsAuth" in result && result.needsAuth) {
|
|
290
303
|
window.location.href = result.authUrl;
|
|
291
304
|
return;
|
|
@@ -302,58 +315,100 @@ export default function Sidebar() {
|
|
|
302
315
|
}
|
|
303
316
|
};
|
|
304
317
|
|
|
305
|
-
const
|
|
306
|
-
setTopicDriveStatus((prev) => ({ ...prev, [topicId]: value }));
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
const handlePullTopicFromDrive = async (topicId: string) => {
|
|
318
|
+
const pushTopicToDrive = async (topicId: string, targetFolderId: string) => {
|
|
310
319
|
if (!activeWorkspaceId || !activeWs?.driveConfig?.folderId) return;
|
|
311
|
-
|
|
312
|
-
setTopicStatus(topicId, "
|
|
320
|
+
setTopicPushingId(topicId);
|
|
321
|
+
setTopicStatus(topicId, "Pushing topic to Drive…");
|
|
313
322
|
try {
|
|
314
|
-
const result = await
|
|
323
|
+
const result = await exportTopic(activeWorkspaceId, topicId, targetFolderId);
|
|
315
324
|
if ("needsAuth" in result && result.needsAuth) {
|
|
316
325
|
window.location.href = result.authUrl;
|
|
317
326
|
return;
|
|
318
327
|
}
|
|
319
|
-
const firstError = result.errors[0];
|
|
320
328
|
setTopicStatus(
|
|
321
329
|
topicId,
|
|
322
330
|
result.errors.length > 0
|
|
323
|
-
? `Topic
|
|
324
|
-
: `
|
|
331
|
+
? `Topic push finished with ${result.errors.length} error${result.errors.length === 1 ? "" : "s"}.`
|
|
332
|
+
: `Pushed ${result.questionsExported} question${result.questionsExported === 1 ? "" : "s"} and ${result.filesExported} file${result.filesExported === 1 ? "" : "s"} to Drive.`,
|
|
325
333
|
);
|
|
326
334
|
} catch (err: any) {
|
|
327
|
-
setTopicStatus(topicId, err?.message || "Topic
|
|
335
|
+
setTopicStatus(topicId, err?.message || "Topic push failed.");
|
|
328
336
|
} finally {
|
|
329
|
-
|
|
337
|
+
setTopicPushingId(null);
|
|
330
338
|
}
|
|
331
339
|
};
|
|
332
340
|
|
|
333
|
-
const
|
|
341
|
+
const openPushDestinationPicker = async (
|
|
342
|
+
mode: "workspace" | "topic",
|
|
343
|
+
topicId?: string,
|
|
344
|
+
) => {
|
|
334
345
|
if (!activeWorkspaceId || !activeWs?.driveConfig?.folderId) return;
|
|
335
|
-
|
|
336
|
-
setTopicStatus(topicId, "Pushing topic to Drive…");
|
|
346
|
+
setPushPicker({ mode, topicId, folders: [], loading: true });
|
|
337
347
|
try {
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
348
|
+
const folders = await fetchDriveSubfolders(activeWorkspaceId);
|
|
349
|
+
setPushPicker({ mode, topicId, folders, loading: false });
|
|
350
|
+
} catch (err: any) {
|
|
351
|
+
setPushPicker({ mode, topicId, folders: [], loading: false });
|
|
352
|
+
const message = err?.message || "Failed to load Drive folders.";
|
|
353
|
+
if (mode === "topic" && topicId) setTopicStatus(topicId, message);
|
|
354
|
+
else setDriveFileSyncStatus(message);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const handlePushToDrive = () => {
|
|
359
|
+
if (!activeWs?.driveConfig?.folderId) return;
|
|
360
|
+
if (currentSubFolder) {
|
|
361
|
+
void pushWorkspaceToDrive(currentSubFolder.id);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
void openPushDestinationPicker("workspace");
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const handlePushTopicToDrive = (topicId: string) => {
|
|
368
|
+
if (!activeWs?.driveConfig?.folderId) return;
|
|
369
|
+
if (currentSubFolder) {
|
|
370
|
+
void pushTopicToDrive(topicId, currentSubFolder.id);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
void openPushDestinationPicker("topic", topicId);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const handlePushPickerDestination = (targetFolderId: string) => {
|
|
377
|
+
const picker = pushPicker;
|
|
378
|
+
if (!picker) return;
|
|
379
|
+
setPushPicker(null);
|
|
380
|
+
if (picker.mode === "topic" && picker.topicId) {
|
|
381
|
+
void pushTopicToDrive(picker.topicId, targetFolderId);
|
|
382
|
+
} else {
|
|
383
|
+
void pushWorkspaceToDrive(targetFolderId);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const setTopicStatus = (topicId: string, value: string) => {
|
|
388
|
+
setTopicDriveStatus((prev) => ({ ...prev, [topicId]: value }));
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const handlePullTopicFromDrive = async (topicId: string) => {
|
|
392
|
+
if (!activeWorkspaceId || !activeWs?.driveConfig?.folderId) return;
|
|
393
|
+
setTopicSyncingId(topicId);
|
|
394
|
+
setTopicStatus(topicId, "Pulling topic from Drive…");
|
|
395
|
+
try {
|
|
396
|
+
const result = await syncTopic(activeWorkspaceId, topicId);
|
|
343
397
|
if ("needsAuth" in result && result.needsAuth) {
|
|
344
398
|
window.location.href = result.authUrl;
|
|
345
399
|
return;
|
|
346
400
|
}
|
|
401
|
+
const firstError = result.errors[0];
|
|
347
402
|
setTopicStatus(
|
|
348
403
|
topicId,
|
|
349
404
|
result.errors.length > 0
|
|
350
|
-
? `Topic
|
|
351
|
-
: `
|
|
405
|
+
? `Topic pull finished with ${result.errors.length} error${result.errors.length === 1 ? "" : "s"}. ${firstError ? `First: ${firstError}` : ""}`
|
|
406
|
+
: `Pulled ${result.filesImported} file${result.filesImported === 1 ? "" : "s"} into this topic.`,
|
|
352
407
|
);
|
|
353
408
|
} catch (err: any) {
|
|
354
|
-
setTopicStatus(topicId, err?.message || "Topic
|
|
409
|
+
setTopicStatus(topicId, err?.message || "Topic pull failed.");
|
|
355
410
|
} finally {
|
|
356
|
-
|
|
411
|
+
setTopicSyncingId(null);
|
|
357
412
|
}
|
|
358
413
|
};
|
|
359
414
|
|
|
@@ -1020,7 +1075,142 @@ export default function Sidebar() {
|
|
|
1020
1075
|
};
|
|
1021
1076
|
|
|
1022
1077
|
return (
|
|
1023
|
-
|
|
1078
|
+
<>
|
|
1079
|
+
{pushPicker && (
|
|
1080
|
+
<div
|
|
1081
|
+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
|
|
1082
|
+
onClick={() => !pushPicker.loading && setPushPicker(null)}
|
|
1083
|
+
>
|
|
1084
|
+
<div
|
|
1085
|
+
className="w-80 rounded-xl border border-slate-700 bg-slate-900 p-4 shadow-2xl"
|
|
1086
|
+
onClick={(e) => e.stopPropagation()}
|
|
1087
|
+
>
|
|
1088
|
+
<div className="mb-3 flex items-center justify-between gap-3">
|
|
1089
|
+
<div>
|
|
1090
|
+
<h3 className="text-sm font-semibold text-slate-200">
|
|
1091
|
+
Choose Drive destination
|
|
1092
|
+
</h3>
|
|
1093
|
+
<p className="mt-0.5 text-[11px] text-slate-500">
|
|
1094
|
+
{pushPicker.mode === "topic"
|
|
1095
|
+
? "Push this topic into the selected Drive folder."
|
|
1096
|
+
: "Push this local workspace into the selected Drive folder."}
|
|
1097
|
+
</p>
|
|
1098
|
+
</div>
|
|
1099
|
+
<button
|
|
1100
|
+
onClick={() => setPushPicker(null)}
|
|
1101
|
+
className="text-slate-500 hover:text-slate-300"
|
|
1102
|
+
disabled={pushPicker.loading}
|
|
1103
|
+
>
|
|
1104
|
+
<X className="h-4 w-4" />
|
|
1105
|
+
</button>
|
|
1106
|
+
</div>
|
|
1107
|
+
|
|
1108
|
+
{pushPicker.loading ? (
|
|
1109
|
+
<div className="flex items-center justify-center gap-2 py-6 text-xs text-slate-400">
|
|
1110
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
1111
|
+
Loading Drive folders…
|
|
1112
|
+
</div>
|
|
1113
|
+
) : (
|
|
1114
|
+
<>
|
|
1115
|
+
<div className="max-h-52 space-y-1 overflow-y-auto">
|
|
1116
|
+
{activeWs?.driveConfig?.folderId && (
|
|
1117
|
+
<button
|
|
1118
|
+
type="button"
|
|
1119
|
+
onClick={() =>
|
|
1120
|
+
handlePushPickerDestination(activeWs.driveConfig!.folderId)
|
|
1121
|
+
}
|
|
1122
|
+
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-xs text-slate-300 hover:bg-slate-800"
|
|
1123
|
+
>
|
|
1124
|
+
<FolderOpen className="h-3.5 w-3.5 shrink-0 text-slate-500" />
|
|
1125
|
+
<span className="min-w-0 flex-1 truncate italic text-slate-400">
|
|
1126
|
+
{activeWs.driveConfig.folderName || "Linked root folder"}
|
|
1127
|
+
</span>
|
|
1128
|
+
</button>
|
|
1129
|
+
)}
|
|
1130
|
+
{pushPicker.folders.map((folder) => (
|
|
1131
|
+
<button
|
|
1132
|
+
key={folder.id}
|
|
1133
|
+
type="button"
|
|
1134
|
+
onClick={() => handlePushPickerDestination(folder.id)}
|
|
1135
|
+
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-xs text-slate-200 hover:bg-slate-800"
|
|
1136
|
+
>
|
|
1137
|
+
<FolderOpen className="h-3.5 w-3.5 shrink-0 text-cyan-400" />
|
|
1138
|
+
<span className="min-w-0 flex-1 truncate">
|
|
1139
|
+
{folder.name}
|
|
1140
|
+
</span>
|
|
1141
|
+
</button>
|
|
1142
|
+
))}
|
|
1143
|
+
{pushPicker.folders.length === 0 && (
|
|
1144
|
+
<p className="px-3 py-2 text-xs text-slate-500">
|
|
1145
|
+
No subfolders found. Choose the linked root folder above
|
|
1146
|
+
or create a new folder below.
|
|
1147
|
+
</p>
|
|
1148
|
+
)}
|
|
1149
|
+
</div>
|
|
1150
|
+
|
|
1151
|
+
<form
|
|
1152
|
+
className="mt-3 flex gap-2"
|
|
1153
|
+
onSubmit={async (e) => {
|
|
1154
|
+
e.preventDefault();
|
|
1155
|
+
const name = pushPickerNewFolderName.trim();
|
|
1156
|
+
if (!name || !activeWorkspaceId) return;
|
|
1157
|
+
setPushPickerCreatingFolder(true);
|
|
1158
|
+
try {
|
|
1159
|
+
const created = await createDriveSubfolder(
|
|
1160
|
+
activeWorkspaceId,
|
|
1161
|
+
name,
|
|
1162
|
+
);
|
|
1163
|
+
if ("needsAuth" in created) {
|
|
1164
|
+
window.location.href = created.authUrl;
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
setPushPicker((prev) =>
|
|
1168
|
+
prev
|
|
1169
|
+
? { ...prev, folders: [...prev.folders, created] }
|
|
1170
|
+
: prev,
|
|
1171
|
+
);
|
|
1172
|
+
setPushPickerNewFolderName("");
|
|
1173
|
+
} catch (err: any) {
|
|
1174
|
+
const message = err?.message || "Failed to create folder";
|
|
1175
|
+
if (pushPicker.mode === "topic" && pushPicker.topicId) {
|
|
1176
|
+
setTopicStatus(pushPicker.topicId, message);
|
|
1177
|
+
} else {
|
|
1178
|
+
setDriveFileSyncStatus(message);
|
|
1179
|
+
}
|
|
1180
|
+
} finally {
|
|
1181
|
+
setPushPickerCreatingFolder(false);
|
|
1182
|
+
}
|
|
1183
|
+
}}
|
|
1184
|
+
>
|
|
1185
|
+
<input
|
|
1186
|
+
value={pushPickerNewFolderName}
|
|
1187
|
+
onChange={(e) => setPushPickerNewFolderName(e.target.value)}
|
|
1188
|
+
placeholder="New folder name…"
|
|
1189
|
+
className="min-w-0 flex-1 rounded-lg border border-slate-700 bg-slate-800 px-2 py-1.5 text-xs text-slate-200 placeholder:text-slate-500 focus:border-cyan-500 focus:outline-none"
|
|
1190
|
+
/>
|
|
1191
|
+
<button
|
|
1192
|
+
type="submit"
|
|
1193
|
+
disabled={
|
|
1194
|
+
!pushPickerNewFolderName.trim() ||
|
|
1195
|
+
pushPickerCreatingFolder
|
|
1196
|
+
}
|
|
1197
|
+
className="flex items-center gap-1 rounded-lg bg-cyan-700 px-2.5 py-1.5 text-xs font-medium text-white hover:bg-cyan-600 disabled:opacity-40"
|
|
1198
|
+
>
|
|
1199
|
+
{pushPickerCreatingFolder ? (
|
|
1200
|
+
<Loader2 className="h-3 w-3 animate-spin" />
|
|
1201
|
+
) : (
|
|
1202
|
+
<Plus className="h-3 w-3" />
|
|
1203
|
+
)}
|
|
1204
|
+
Create
|
|
1205
|
+
</button>
|
|
1206
|
+
</form>
|
|
1207
|
+
</>
|
|
1208
|
+
)}
|
|
1209
|
+
</div>
|
|
1210
|
+
</div>
|
|
1211
|
+
)}
|
|
1212
|
+
|
|
1213
|
+
<aside className="w-72 border-r border-slate-800 flex flex-col bg-slate-900/50 shrink-0">
|
|
1024
1214
|
{/* Workspace switcher */}
|
|
1025
1215
|
<WorkspaceSwitcher />
|
|
1026
1216
|
|
|
@@ -1060,6 +1250,32 @@ export default function Sidebar() {
|
|
|
1060
1250
|
sync from inside that selected folder.
|
|
1061
1251
|
</p>
|
|
1062
1252
|
)}
|
|
1253
|
+
{canPushLocalDrive && (
|
|
1254
|
+
<div className="mt-2 space-y-1.5">
|
|
1255
|
+
<p className="text-[10px] text-slate-600">
|
|
1256
|
+
Push local workspace to Drive
|
|
1257
|
+
</p>
|
|
1258
|
+
<button
|
|
1259
|
+
type="button"
|
|
1260
|
+
onClick={handlePushToDrive}
|
|
1261
|
+
disabled={pushing}
|
|
1262
|
+
className="flex w-full items-center justify-center gap-1 rounded-md border border-cyan-500/20 bg-cyan-500/10 px-2 py-1 text-[11px] font-medium text-cyan-300 transition-colors hover:bg-cyan-500/15 disabled:opacity-40 disabled:hover:bg-cyan-500/10"
|
|
1263
|
+
title="Choose a Drive folder and push topics, questions, and workspace files into it"
|
|
1264
|
+
>
|
|
1265
|
+
{pushing ? (
|
|
1266
|
+
<Loader2 className="w-3 h-3 animate-spin" />
|
|
1267
|
+
) : (
|
|
1268
|
+
<Upload className="w-3 h-3" />
|
|
1269
|
+
)}
|
|
1270
|
+
{pushing ? "Pushing…" : "Choose folder & push"}
|
|
1271
|
+
</button>
|
|
1272
|
+
{driveFileSyncStatus && (
|
|
1273
|
+
<p className="text-[10px] leading-relaxed text-slate-500">
|
|
1274
|
+
{driveFileSyncStatus}
|
|
1275
|
+
</p>
|
|
1276
|
+
)}
|
|
1277
|
+
</div>
|
|
1278
|
+
)}
|
|
1063
1279
|
{canSyncDriveFolder && (
|
|
1064
1280
|
<div className="mt-2 space-y-1.5">
|
|
1065
1281
|
<p className="text-[10px] text-slate-600">
|
|
@@ -1393,13 +1609,13 @@ export default function Sidebar() {
|
|
|
1393
1609
|
<Download className="w-3 h-3" /> Download
|
|
1394
1610
|
</button>
|
|
1395
1611
|
|
|
1396
|
-
{
|
|
1612
|
+
{canPushDriveDestination && (
|
|
1397
1613
|
<>
|
|
1398
1614
|
<div className="border-t border-slate-700 my-0.5" />
|
|
1399
1615
|
<button
|
|
1400
1616
|
onClick={() => {
|
|
1401
1617
|
setOpenMenuTopicId(null);
|
|
1402
|
-
|
|
1618
|
+
handlePushTopicToDrive(topic.id);
|
|
1403
1619
|
}}
|
|
1404
1620
|
disabled={topicBusy || pushing || syncing}
|
|
1405
1621
|
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-cyan-300 hover:bg-slate-700 hover:text-cyan-200 disabled:opacity-50 transition-colors"
|
|
@@ -1411,21 +1627,23 @@ export default function Sidebar() {
|
|
|
1411
1627
|
)}
|
|
1412
1628
|
Push topic
|
|
1413
1629
|
</button>
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1630
|
+
{canSyncDriveFolder && (
|
|
1631
|
+
<button
|
|
1632
|
+
onClick={() => {
|
|
1633
|
+
setOpenMenuTopicId(null);
|
|
1634
|
+
void handlePullTopicFromDrive(topic.id);
|
|
1635
|
+
}}
|
|
1636
|
+
disabled={topicBusy || pushing || syncing}
|
|
1637
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-slate-300 hover:bg-slate-700 hover:text-cyan-300 disabled:opacity-50 transition-colors"
|
|
1638
|
+
>
|
|
1639
|
+
{topicSyncingId === topic.id ? (
|
|
1640
|
+
<Loader2 className="w-3 h-3 animate-spin" />
|
|
1641
|
+
) : (
|
|
1642
|
+
<RefreshCw className="w-3 h-3" />
|
|
1643
|
+
)}
|
|
1644
|
+
Pull topic
|
|
1645
|
+
</button>
|
|
1646
|
+
)}
|
|
1429
1647
|
</>
|
|
1430
1648
|
)}
|
|
1431
1649
|
|
|
@@ -1555,6 +1773,7 @@ export default function Sidebar() {
|
|
|
1555
1773
|
})}
|
|
1556
1774
|
</div>
|
|
1557
1775
|
)}
|
|
1558
|
-
|
|
1776
|
+
</aside>
|
|
1777
|
+
</>
|
|
1559
1778
|
);
|
|
1560
1779
|
}
|
package/template/cockpit.json
CHANGED
|
@@ -1181,6 +1181,13 @@ async function resolveExportFolderId(
|
|
|
1181
1181
|
if (!ws.driveConfig?.folderId) {
|
|
1182
1182
|
throw new Error("No Drive folder linked to this workspace");
|
|
1183
1183
|
}
|
|
1184
|
+
|
|
1185
|
+
// Local workspaces do not have a Drive-navigation state, so a missing
|
|
1186
|
+
// target should mean "the linked folder itself" rather than silently
|
|
1187
|
+
// creating/using _export. The UI now prompts for a destination, but this
|
|
1188
|
+
// server-side fallback protects older/stale clients too.
|
|
1189
|
+
if (ws.type === "local") return ws.driveConfig.folderId;
|
|
1190
|
+
|
|
1184
1191
|
return getOrCreateFolder(drive, ws.driveConfig.folderId, EXPORT_FOLDER_NAME);
|
|
1185
1192
|
}
|
|
1186
1193
|
|