@ynput/ayon-addon-dev 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,1635 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useCallback, createContext, useContext, useState, useMemo, useEffect, useSyncExternalStore, Component, StrictMode } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import { provideHost, isKnownSlot, getRegistry, contributionTitle, getHost, registerManifest, setManifestsLoading } from "@ynput/ayon-addon/host";
5
+ import { useGetProjectFoldersQuery, useGetCurrentUserQuery, useListProjectsQuery, useGetEntitiesDetailsPanelQuery, useGetProjectQuery, useGetUsersAssigneeQuery, createStandaloneStore } from "@ynput/ayon-frontend-shared/api";
6
+ import { SlicerHost, ProjectPageLayoutHost, JsonViewer, AddonButton, useAddonCustomizeSections, useAddonTableShortcuts, SettingsPanel, AddonDialog, useStudioPageSlotContext, useProjectPageSlotContext, SplitterStyles, LoginPage } from "@ynput/ayon-frontend-shared/components";
7
+ import { useGlobalContext, usePowerpack, useMenuContext, ProjectContextProvider, ProjectFoldersContextProvider, useSettingsPanel, useScopedDetailsPanel, EntityUpdatesProvider, GlobalProvider, PowerpackProvider, MenuProvider, URIProvider, DetailsPanelProvider, PiPProvider, DeleteEntitiesProvider, SettingsPanelProvider } from "@ynput/ayon-frontend-shared/context";
8
+ import { buildProjectsTableData, ProjectsTable, PROJECTS_LIST_MENU_ID, parseProjectFolderRowId } from "@ynput/ayon-frontend-shared/ProjectsTable";
9
+ import { getProjectDisplayName, clearAccessToken, storeAccessToken } from "@ynput/ayon-frontend-shared/util";
10
+ import { Button, Icon, Dropdown, Dialog, InputText } from "@ynput/ayon-react-components";
11
+ import { useContributions, prefetchPiece, runContribution, ContributionView } from "@ynput/ayon-addon";
12
+ import { useCreateContextMenu, ContextMenuProvider, GlobalContextMenu } from "@ynput/ayon-frontend-shared/ContextMenu";
13
+ import { useDispatch, Provider } from "react-redux";
14
+ import { BrowserRouter, useNavigate, useSearchParams, useParams, useLocation } from "react-router-dom";
15
+ import { ToastContainer } from "react-toastify";
16
+ import { Splitter, SplitterPanel } from "primereact/splitter";
17
+ import { useAddonTableColumns, useAddonEntityMenuItems, AddonColumnDataScope, ProjectDataProvider } from "@ynput/ayon-frontend-shared/ProjectTreeTable";
18
+ import { SLOT_DOCS } from "@ynput/ayon-addon/check";
19
+ import { EntityPickerDialog } from "@ynput/ayon-frontend-shared/EntityPickerDialog";
20
+ import { DetailsPanel } from "@ynput/ayon-frontend-shared/DetailsPanel";
21
+ import { NewEntityProvider } from "@ynput/ayon-frontend-shared/NewEntity";
22
+ import { SlicerProvider, useSlicerContext } from "@ynput/ayon-frontend-shared/Slicer";
23
+ import { ViewsProvider } from "@ynput/ayon-frontend-shared/Views";
24
+ import { useReactTable, getCoreRowModel, flexRender } from "@tanstack/react-table";
25
+ const inMenu = (item, menu) => {
26
+ const kinds = item.in;
27
+ return !kinds?.length || kinds.includes(menu);
28
+ };
29
+ const useShellMenu = (slot, makeContext, applies = () => true) => {
30
+ const { contributions } = useContributions(slot);
31
+ const [openMenu] = useCreateContextMenu([]);
32
+ const itemsFor = useCallback(
33
+ (menu, extra) => {
34
+ const context = { ...makeContext(menu), ...extra };
35
+ return contributions.filter((item) => inMenu(item, menu) && applies(item)).map((item) => {
36
+ prefetchPiece(item.run);
37
+ return {
38
+ id: item.key,
39
+ label: item.label,
40
+ icon: item.icon,
41
+ command: () => runContribution(item, context)
42
+ };
43
+ });
44
+ },
45
+ [contributions, makeContext, applies]
46
+ );
47
+ const openContextMenu = (e, extra) => {
48
+ e.preventDefault();
49
+ const items = itemsFor("context", extra);
50
+ if (items.length) openMenu(e, items);
51
+ };
52
+ const hasDropdownItems = contributions.some(
53
+ (item) => inMenu(item, "dropdown") && applies(item)
54
+ );
55
+ const dropdown = hasDropdownItems ? /* @__PURE__ */ jsx(
56
+ Button,
57
+ {
58
+ icon: "more_vert",
59
+ variant: "text",
60
+ "data-shell-menu": "dropdown",
61
+ onClick: (e) => openMenu(e, itemsFor("dropdown"))
62
+ }
63
+ ) : null;
64
+ return { openContextMenu, dropdown, itemsFor };
65
+ };
66
+ const useUser = () => {
67
+ const { data: user2 } = useGetCurrentUserQuery();
68
+ return useMemo(() => {
69
+ if (!user2?.name) return void 0;
70
+ const data = user2.data || {};
71
+ const isAdmin = !!data.isAdmin || !!data.isService;
72
+ return {
73
+ name: user2.name,
74
+ fullName: user2.attrib?.fullName || void 0,
75
+ email: user2.attrib?.email || void 0,
76
+ isAdmin,
77
+ isManager: isAdmin || !!data.isManager,
78
+ isGuest: !!data.isGuest,
79
+ isDeveloper: !!user2.attrib?.developerMode || !!data.isService
80
+ };
81
+ }, [user2]);
82
+ };
83
+ const StudioSelectionContext = createContext(null);
84
+ const StudioPageLayout = ({ sidebar, children }) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", width: "100%", height: "100%", minHeight: 0 }, children: [
85
+ sidebar && /* @__PURE__ */ jsx(
86
+ "div",
87
+ {
88
+ style: {
89
+ width: 240,
90
+ flexShrink: 0,
91
+ overflow: "auto",
92
+ borderRight: "1px solid var(--md-sys-color-outline-variant)"
93
+ },
94
+ children: sidebar
95
+ }
96
+ ),
97
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0, overflow: "hidden", display: "flex" }, children })
98
+ ] });
99
+ const ProjectList = ({ multiSelect = false }) => {
100
+ const studio = useContext(StudioSelectionContext);
101
+ const { projects: allProjects, isLoading } = useGlobalContext();
102
+ const { powerLicense } = usePowerpack();
103
+ const { data: folders } = useGetProjectFoldersQuery();
104
+ const { toggleMenuOpen } = useMenuContext();
105
+ const [expanded, setExpanded] = useState({});
106
+ const [search, setSearch] = useState();
107
+ const selection = useMemo(() => studio?.selection || [], [studio?.selection]);
108
+ const projectNamesOf = (ids) => ids.filter((id) => !parseProjectFolderRowId(id));
109
+ const { itemsFor } = useShellMenu("projectList.menuItem", (menu) => ({
110
+ projectNames: projectNamesOf(selection),
111
+ menu
112
+ }));
113
+ const projects = useMemo(
114
+ () => [...allProjects.active].sort(
115
+ (a, b) => getProjectDisplayName(a).localeCompare(getProjectDisplayName(b))
116
+ ),
117
+ [allProjects.active]
118
+ );
119
+ const showFolders = !search && powerLicense;
120
+ const data = useMemo(
121
+ () => buildProjectsTableData(projects, showFolders ? folders : [], true, powerLicense),
122
+ [projects, folders, showFolders, powerLicense]
123
+ );
124
+ const onSelect = studio?.onSelect;
125
+ useEffect(() => {
126
+ if (onSelect && !selection.length && projects.length) onSelect([projects[0].name]);
127
+ }, [onSelect, selection, projects]);
128
+ if (!studio) {
129
+ return /* @__PURE__ */ jsx("div", { role: "alert", className: "ayon-addon-error", children: "ProjectList only works on a studio page." });
130
+ }
131
+ return /* @__PURE__ */ jsx("div", { "data-shell-surface": "project-list", style: { display: "flex", height: "100%" }, children: /* @__PURE__ */ jsx(
132
+ ProjectsTable,
133
+ {
134
+ data,
135
+ isLoading: isLoading.projects,
136
+ search,
137
+ onSearch: setSearch,
138
+ selection,
139
+ onSelect: studio.onSelect,
140
+ onRowSelectionChange: (rows) => studio.onSelect(Object.keys(rows).filter((id) => rows[id])),
141
+ rowPinning: [],
142
+ expanded,
143
+ setExpanded,
144
+ multiSelect,
145
+ hiddenButtons: multiSelect ? [] : ["select-all"],
146
+ onSelectAll: () => studio.onSelect(selection.length === projects.length ? [] : projects.map((p) => p.name)),
147
+ toggleMenu: (open = true) => toggleMenuOpen(open ? PROJECTS_LIST_MENU_ID : false),
148
+ buildMenuItems: () => itemsFor("dropdown").map(({ command, ...item }) => ({ ...item, onClick: command })),
149
+ rowContextMenuBuilders: [
150
+ (_e, context) => itemsFor("context", { projectNames: projectNamesOf(context.selectedRows) })
151
+ ]
152
+ }
153
+ ) });
154
+ };
155
+ const provideShellHost = () => provideHost({
156
+ useUser,
157
+ components: {
158
+ ProjectPageLayout: ProjectPageLayoutHost,
159
+ Slicer: SlicerHost,
160
+ StudioPageLayout,
161
+ ProjectList
162
+ }
163
+ });
164
+ const StudioSelectionProvider = ({
165
+ value,
166
+ children
167
+ }) => /* @__PURE__ */ jsx(StudioSelectionContext.Provider, { value, children });
168
+ const originalFetch = window.fetch.bind(window);
169
+ const PREVIEW_USER = {
170
+ name: "preview",
171
+ attrib: { fullName: "Preview user", email: "preview@example.com" },
172
+ data: { isAdmin: true, isManager: true, isGuest: false },
173
+ uiExposureLevel: 1e3
174
+ };
175
+ const APP_ANSWERS = {
176
+ "/api/users/me": PREVIEW_USER,
177
+ "/api/info": {
178
+ version: "preview",
179
+ user: PREVIEW_USER,
180
+ attributes: [],
181
+ sites: [],
182
+ ssoOptions: [],
183
+ frontendFlags: [],
184
+ extras: "",
185
+ disableChangelog: true
186
+ },
187
+ "/api/projects": { detail: "", count: 0, projects: [] },
188
+ "/api/connect": { connected: false, offlineMode: true }
189
+ };
190
+ const appPath = (key) => {
191
+ const match = key.match(/^GET ([^?]+)/);
192
+ return match && match[1] in APP_ANSWERS ? match[1] : void 0;
193
+ };
194
+ const recorded = /* @__PURE__ */ new Map();
195
+ let fixture;
196
+ const unmatched = /* @__PURE__ */ new Set();
197
+ const used = /* @__PURE__ */ new Set();
198
+ const listeners = /* @__PURE__ */ new Set();
199
+ const isAyonRequest = (url) => url.origin === window.location.origin && (url.pathname.startsWith("/api") || url.pathname.startsWith("/graphql"));
200
+ const requestKey = async (url, method, body) => {
201
+ if (url.pathname.startsWith("/graphql") && typeof body === "string") {
202
+ try {
203
+ const { operationName, query } = JSON.parse(body);
204
+ const name = operationName || query?.match(/(?:query|mutation)\s+(\w+)/)?.[1];
205
+ if (name) return `graphql ${name}`;
206
+ } catch {
207
+ }
208
+ }
209
+ return `${method.toUpperCase()} ${url.pathname}${url.search}`;
210
+ };
211
+ const toRequest = (input, init) => {
212
+ const request = input instanceof Request ? input : void 0;
213
+ const url = new URL(request ? request.url : String(input), window.location.href);
214
+ const method = init?.method || request?.method || "GET";
215
+ return { url, method };
216
+ };
217
+ const readBody = async (input, init) => {
218
+ if (init?.body !== void 0) return init.body;
219
+ if (input instanceof Request && input.method !== "GET") return input.clone().text();
220
+ return void 0;
221
+ };
222
+ const previewAnswer = (key) => {
223
+ const app = appPath(key);
224
+ const answer = fixture?.responses[key] || (app ? { status: 200, body: APP_ANSWERS[app] } : void 0);
225
+ if (!answer) {
226
+ unmatched.add(key);
227
+ listeners.forEach((listener) => listener());
228
+ console.warn(`[ayon-addon preview] no fixture answer for ${key}`);
229
+ return { status: 404, body: { detail: `no fixture answer for ${key}` } };
230
+ }
231
+ if (fixture?.responses[key]) used.add(key);
232
+ return answer;
233
+ };
234
+ const OriginalXHR = window.XMLHttpRequest;
235
+ class PreviewXHR extends OriginalXHR {
236
+ ayonRequest;
237
+ open(method, url, ...rest) {
238
+ const target = new URL(String(url), window.location.href);
239
+ this.ayonRequest = isAyonRequest(target) ? { url: target, method } : void 0;
240
+ return super.open(method, url, ...rest);
241
+ }
242
+ send(body) {
243
+ const request = this.ayonRequest;
244
+ if (!request) return super.send(body);
245
+ void requestKey(request.url, request.method, body).then((key) => {
246
+ const { status, body: answer } = previewAnswer(key);
247
+ const text = JSON.stringify(answer);
248
+ const responses = {
249
+ readyState: 4,
250
+ status,
251
+ statusText: status === 200 ? "OK" : "Not Found",
252
+ responseURL: request.url.href,
253
+ responseText: text,
254
+ response: this.responseType === "json" ? answer : text
255
+ };
256
+ for (const [name, value] of Object.entries(responses)) {
257
+ Object.defineProperty(this, name, { value, configurable: true });
258
+ }
259
+ this.getAllResponseHeaders = () => "content-type: application/json\r\n";
260
+ this.getResponseHeader = (name) => name.toLowerCase() === "content-type" ? "application/json" : null;
261
+ for (const type of ["readystatechange", "load", "loadend"]) this.dispatchEvent(new Event(type));
262
+ });
263
+ }
264
+ }
265
+ const installNetwork = (mode) => {
266
+ if (mode === "preview") window.XMLHttpRequest = PreviewXHR;
267
+ window.fetch = async (input, init) => {
268
+ const { url, method } = toRequest(input, init);
269
+ if (!isAyonRequest(url)) return originalFetch(input, init);
270
+ const key = await requestKey(url, method, await readBody(input, init));
271
+ if (mode === "preview") {
272
+ const answer = previewAnswer(key);
273
+ return new Response(JSON.stringify(answer.body), {
274
+ status: answer.status,
275
+ headers: { "Content-Type": "application/json" }
276
+ });
277
+ }
278
+ const response = await originalFetch(input, init);
279
+ if (response.headers.get("Content-Type")?.includes("application/json")) {
280
+ response.clone().json().then((body) => recorded.set(key, { status: response.status, body })).catch(() => void 0);
281
+ }
282
+ return response;
283
+ };
284
+ };
285
+ const getFixture = () => fixture;
286
+ const setFixture = (next) => {
287
+ fixture = next;
288
+ unmatched.clear();
289
+ used.clear();
290
+ };
291
+ const getRecorded = () => Object.fromEntries([...recorded].filter(([key]) => !appPath(key)));
292
+ const getUnmatched = () => [...unmatched];
293
+ const getUsed = () => [...used];
294
+ const onUnmatched = (listener) => {
295
+ listeners.add(listener);
296
+ return () => {
297
+ listeners.delete(listener);
298
+ };
299
+ };
300
+ const PARAMS = {
301
+ slot: "slot",
302
+ id: "contribution",
303
+ project: "ctxProject",
304
+ projects: "ctxProjects",
305
+ entities: "ctxEntities",
306
+ entityType: "ctxEntityType",
307
+ page: "ctxPage",
308
+ preview: "preview"
309
+ };
310
+ const PAGES = ["overview", "products", "lists"];
311
+ const parseEntities = (value, projectName) => (value || "").split(",").map((part) => part.trim()).filter(Boolean).flatMap((part) => {
312
+ const [entityType, id, ...name] = part.split(":");
313
+ if (!entityType || !id || !projectName) return [];
314
+ return [{ entityType, id, projectName, name: name.join(":") || void 0 }];
315
+ });
316
+ const readSelection = (search = window.location.search) => {
317
+ const params = new URLSearchParams(search);
318
+ const slot = params.get(PARAMS.slot) || void 0;
319
+ const projectName = params.get(PARAMS.project) || void 0;
320
+ const page = params.get(PARAMS.page);
321
+ const entities = parseEntities(params.get(PARAMS.entities), projectName);
322
+ return {
323
+ slot: slot && isKnownSlot(slot) ? slot : void 0,
324
+ id: params.get(PARAMS.id) || void 0,
325
+ projectName,
326
+ projectNames: params.get(PARAMS.projects)?.split(",").filter(Boolean),
327
+ entityType: entities[0]?.entityType || params.get(PARAMS.entityType) || void 0,
328
+ entities,
329
+ page: page && PAGES.includes(page) ? page : void 0,
330
+ preview: params.get(PARAMS.preview) || void 0
331
+ };
332
+ };
333
+ const writeSelection = (selection) => {
334
+ const params = new URLSearchParams(window.location.search);
335
+ const set = (name, value) => value ? params.set(name, value) : params.delete(name);
336
+ set(PARAMS.preview, selection.preview);
337
+ set(PARAMS.slot, selection.slot);
338
+ set(PARAMS.id, selection.id);
339
+ set(PARAMS.project, selection.projectName);
340
+ set(PARAMS.projects, selection.projectNames?.join(",") || void 0);
341
+ set(PARAMS.page, selection.page);
342
+ set(
343
+ PARAMS.entities,
344
+ selection.entities.map((entity) => `${entity.entityType}:${entity.id}`).join(",") || void 0
345
+ );
346
+ set(PARAMS.entityType, selection.entities.length ? void 0 : selection.entityType);
347
+ const search = params.toString();
348
+ window.history.replaceState(
349
+ window.history.state,
350
+ "",
351
+ `${window.location.pathname}${search ? `?${search}` : ""}`
352
+ );
353
+ };
354
+ const SLOT_AREAS = [
355
+ {
356
+ label: "App header",
357
+ icon: "web_asset",
358
+ sections: [{ slots: [{ slot: "header.item", label: "Items" }] }]
359
+ },
360
+ {
361
+ label: "Studio",
362
+ icon: "home",
363
+ sections: [
364
+ { slots: [{ slot: "studio.page", label: "Pages" }] },
365
+ { label: "Project list", slots: [{ slot: "projectList.menuItem", label: "Menu" }] }
366
+ ]
367
+ },
368
+ {
369
+ label: "Project",
370
+ icon: "folder_open",
371
+ sections: [
372
+ { slots: [{ slot: "project.page", label: "Pages" }] },
373
+ { label: "Slicer", slots: [{ slot: "slicer.menuItem", label: "Menu" }] },
374
+ {
375
+ label: "Tables",
376
+ slots: [
377
+ { slot: "tableColumn", label: "Columns" },
378
+ { slot: "entityMenuItem", label: "Cell menu" },
379
+ { slot: "table.shortcut", label: "Shortcuts" },
380
+ { slot: "projectTable.toolbarItem", label: "Toolbar" },
381
+ { slot: "projectTable.customizeSection", label: "Customize panel" }
382
+ ]
383
+ }
384
+ ]
385
+ },
386
+ {
387
+ label: "Details panel",
388
+ icon: "dock_to_left",
389
+ sections: [
390
+ {
391
+ slots: [
392
+ { slot: "detailsPanel.tab", label: "Tabs" },
393
+ { slot: "detailsPanel.menuItem", label: "More menu" }
394
+ ]
395
+ }
396
+ ]
397
+ }
398
+ ];
399
+ const slotPath = (slot) => {
400
+ for (const area of SLOT_AREAS) {
401
+ for (const section of area.sections) {
402
+ const found = section.slots.find((s) => s.slot === slot);
403
+ if (found) return [area.label, ...section.label ? [section.label] : [], found.label];
404
+ }
405
+ }
406
+ return ["Other", slot];
407
+ };
408
+ const slotIcon = (slot) => SLOT_AREAS.find((area) => area.sections.some((s) => s.slots.some((x) => x.slot === slot)))?.icon || "extension";
409
+ const NO_PROJECT = ["studio.page", "header.item", "projectList.menuItem"];
410
+ const PROJECTS = ["studio.page", "projectList.menuItem"];
411
+ const ENTITIES = [
412
+ "detailsPanel.tab",
413
+ "detailsPanel.menuItem",
414
+ "entityMenuItem",
415
+ "tableColumn",
416
+ "table.shortcut"
417
+ ];
418
+ const TABLE_PAGE = [
419
+ "entityMenuItem",
420
+ "table.shortcut",
421
+ "tableColumn",
422
+ "projectTable.toolbarItem",
423
+ "projectTable.customizeSection"
424
+ ];
425
+ const RUN = [
426
+ "detailsPanel.menuItem",
427
+ "projectList.menuItem",
428
+ "slicer.menuItem",
429
+ "entityMenuItem",
430
+ "table.shortcut"
431
+ ];
432
+ const needsProject = (slot) => !!slot && !NO_PROJECT.includes(slot);
433
+ const needsProjects = (slot) => !!slot && PROJECTS.includes(slot);
434
+ const needsEntities = (slot) => !!slot && ENTITIES.includes(slot);
435
+ const needsTablePage = (slot) => !!slot && TABLE_PAGE.includes(slot);
436
+ const isMenuSlot = (slot) => !!slot && RUN.includes(slot);
437
+ const ENTITY_TYPES = ["folder", "task", "product", "version"];
438
+ let current = {
439
+ selection: { entities: [] },
440
+ mode: "live"
441
+ };
442
+ let stageContext;
443
+ let stageRun;
444
+ let stageReady = false;
445
+ let user;
446
+ const jsonSafe = (value) => JSON.parse(
447
+ JSON.stringify(
448
+ value,
449
+ (_key, item) => typeof item === "function" || item instanceof Element ? void 0 : item
450
+ )
451
+ );
452
+ const stageListeners = /* @__PURE__ */ new Set();
453
+ const subscribeStage = (listener) => {
454
+ stageListeners.add(listener);
455
+ return () => {
456
+ stageListeners.delete(listener);
457
+ };
458
+ };
459
+ const getStageContext = () => stageContext;
460
+ const publish = () => {
461
+ window.__AYON_ADDON_SHELL__ = {
462
+ ready: stageReady,
463
+ mode: current.mode,
464
+ selection: current.selection,
465
+ unmatched: getUnmatched(),
466
+ get used() {
467
+ return getUsed();
468
+ },
469
+ fixture: () => {
470
+ const { slot, id } = current.selection;
471
+ if (!slot || !id || !stageContext) return void 0;
472
+ return { slot, id, context: jsonSafe(stageContext), user, responses: getRecorded() };
473
+ },
474
+ run: async () => {
475
+ if (!stageRun) throw new Error("the selected contribution has no run piece");
476
+ await stageRun();
477
+ }
478
+ };
479
+ };
480
+ const setStatus = (next) => {
481
+ current = { ...current, ...next };
482
+ publish();
483
+ };
484
+ const setStage = (stage) => {
485
+ stageContext = stage.context;
486
+ stageRun = stage.run;
487
+ stageReady = stage.ready && !getRegistry().isLoading;
488
+ publish();
489
+ stageListeners.forEach((listener) => listener());
490
+ };
491
+ const idOf$1 = (contribution) => contribution.id;
492
+ const showContext = (context) => context ? JSON.stringify(
493
+ context,
494
+ (_key, value) => typeof value === "function" ? `ƒ ${value.name || "function"}` : value instanceof Element ? `<${value.tagName.toLowerCase()}>` : value,
495
+ 2
496
+ ) : "—";
497
+ const Contribution = ({
498
+ contribution,
499
+ selected,
500
+ onClick
501
+ }) => {
502
+ const { icon } = contribution;
503
+ const id = idOf$1(contribution);
504
+ return /* @__PURE__ */ jsxs(
505
+ "button",
506
+ {
507
+ className: `contribution${selected ? " selected" : ""}`,
508
+ "data-contribution": `${contribution.slot}:${id}`,
509
+ title: id,
510
+ onClick,
511
+ children: [
512
+ /* @__PURE__ */ jsx(Icon, { icon: icon || "extension" }),
513
+ /* @__PURE__ */ jsx("span", { children: contributionTitle(contribution) })
514
+ ]
515
+ }
516
+ );
517
+ };
518
+ const Contributions = ({ selection, onChange }) => {
519
+ const { contributions, skipped } = getRegistry();
520
+ const [collapsed, setCollapsed] = useState([]);
521
+ const known = SLOT_AREAS.flatMap((area) => area.sections.flatMap((s) => s.slots.map((x) => x.slot)));
522
+ const other = [...new Set(contributions.map((c) => c.slot))].filter((slot) => !known.includes(slot));
523
+ const areas = other.length ? [
524
+ ...SLOT_AREAS,
525
+ {
526
+ label: "Other",
527
+ icon: "extension",
528
+ sections: [{ label: void 0, slots: other.map((slot) => ({ slot, label: slot })) }]
529
+ }
530
+ ] : SLOT_AREAS;
531
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
532
+ areas.map((area) => {
533
+ const slots = area.sections.flatMap(
534
+ (section) => section.slots.map(({ slot, label }) => ({
535
+ slot,
536
+ label,
537
+ section: section.label,
538
+ items: contributions.filter((c) => c.slot === slot)
539
+ })).filter(({ items }) => items.length)
540
+ );
541
+ if (!slots.length) return null;
542
+ const isCollapsed = collapsed.includes(area.label);
543
+ const count = slots.reduce((sum, { items }) => sum + items.length, 0);
544
+ const slotCount = area.sections.reduce((sum, section) => sum + section.slots.length, 0);
545
+ return /* @__PURE__ */ jsxs("section", { className: "ayon-shell-area", children: [
546
+ /* @__PURE__ */ jsxs(
547
+ "button",
548
+ {
549
+ className: "ayon-shell-area-header",
550
+ "aria-expanded": !isCollapsed,
551
+ onClick: () => setCollapsed(
552
+ isCollapsed ? collapsed.filter((g) => g !== area.label) : [...collapsed, area.label]
553
+ ),
554
+ children: [
555
+ /* @__PURE__ */ jsx(Icon, { icon: area.icon }),
556
+ /* @__PURE__ */ jsx("span", { children: area.label }),
557
+ /* @__PURE__ */ jsx("small", { children: count }),
558
+ /* @__PURE__ */ jsx(Icon, { icon: isCollapsed ? "expand_more" : "expand_less", className: "toggle" })
559
+ ]
560
+ }
561
+ ),
562
+ !isCollapsed && slots.map(({ slot, label, section, items }) => /* @__PURE__ */ jsxs("div", { className: "ayon-shell-slot", children: [
563
+ slotCount > 1 && /* @__PURE__ */ jsxs(
564
+ "div",
565
+ {
566
+ className: "ayon-shell-slot-label",
567
+ title: `${slot}: ${SLOT_DOCS[slot]?.where || ""}`,
568
+ children: [
569
+ section && /* @__PURE__ */ jsx("span", { children: section }),
570
+ label
571
+ ]
572
+ }
573
+ ),
574
+ items.map((c) => /* @__PURE__ */ jsx(
575
+ Contribution,
576
+ {
577
+ contribution: c,
578
+ selected: selection.slot === slot && selection.id === idOf$1(c),
579
+ onClick: () => onChange({ ...selection, slot, id: idOf$1(c) })
580
+ },
581
+ c.key
582
+ ))
583
+ ] }, slot))
584
+ ] }, area.label);
585
+ }),
586
+ skipped.length > 0 && /* @__PURE__ */ jsxs("section", { className: "ayon-shell-area", children: [
587
+ /* @__PURE__ */ jsx("div", { className: "ayon-shell-slot-label", children: "Skipped" }),
588
+ skipped.map((s) => /* @__PURE__ */ jsxs("div", { className: "ayon-addon-error", role: "alert", children: [
589
+ s.slot,
590
+ " ",
591
+ s.id,
592
+ ": ",
593
+ s.reason
594
+ ] }, `${s.slot}:${s.id}`))
595
+ ] })
596
+ ] });
597
+ };
598
+ const Field = ({ label, children }) => /* @__PURE__ */ jsxs("div", { className: "ayon-shell-field", children: [
599
+ /* @__PURE__ */ jsx("label", { children: label }),
600
+ /* @__PURE__ */ jsx("div", { children })
601
+ ] });
602
+ const ProjectPicker = ({ selection, onChange }) => {
603
+ const { data: projects = [] } = useListProjectsQuery({ active: true });
604
+ useEffect(() => {
605
+ if (!selection.projectName && projects.length) {
606
+ onChange({ ...selection, projectName: projects[0].name, entities: [] });
607
+ }
608
+ }, [projects, selection, onChange]);
609
+ return /* @__PURE__ */ jsx(Field, { label: "Project", children: /* @__PURE__ */ jsx(
610
+ Dropdown,
611
+ {
612
+ value: selection.projectName ? [selection.projectName] : [],
613
+ options: projects.map((p) => ({ value: p.name, label: p.name })),
614
+ search: true,
615
+ onChange: (added) => onChange({ ...selection, projectName: added[0], entities: [] })
616
+ }
617
+ ) });
618
+ };
619
+ const ProjectsPicker = ({ selection, onChange }) => {
620
+ const { data: projects = [] } = useListProjectsQuery({ active: true });
621
+ const value = selection.projectNames ?? (selection.projectName ? [selection.projectName] : []);
622
+ return /* @__PURE__ */ jsx(Field, { label: "Projects", children: /* @__PURE__ */ jsx(
623
+ Dropdown,
624
+ {
625
+ value,
626
+ options: projects.map((p) => ({ value: p.name, label: p.name })),
627
+ multiSelect: true,
628
+ search: true,
629
+ placeholder: "None selected",
630
+ onChange: (names) => onChange({ ...selection, projectNames: names })
631
+ }
632
+ ) });
633
+ };
634
+ const EntitiesPicker = ({ selection, onChange }) => {
635
+ const [picking, setPicking] = useState(false);
636
+ const entityType = selection.entityType || "task";
637
+ const { projectName, entities } = selection;
638
+ const { data: details = [] } = useGetEntitiesDetailsPanelQuery(
639
+ { entityType, entities: entities.map((e) => ({ id: e.id, projectName: e.projectName })) },
640
+ { skip: !entities.length || entities.some((e) => e.name) }
641
+ );
642
+ useEffect(() => {
643
+ if (!details.length) return;
644
+ const named = entities.map((entity) => {
645
+ const found = details.find(
646
+ (d) => d.id === entity.id
647
+ );
648
+ return found ? { ...entity, name: found.name, label: found.label || void 0 } : entity;
649
+ });
650
+ onChange({ ...selection, entities: named });
651
+ }, [details]);
652
+ if (!projectName) return null;
653
+ return /* @__PURE__ */ jsxs(Field, { label: "Entities", children: [
654
+ /* @__PURE__ */ jsxs("div", { className: "ayon-shell-entities", children: [
655
+ /* @__PURE__ */ jsx(
656
+ Dropdown,
657
+ {
658
+ value: [entityType],
659
+ options: ENTITY_TYPES.map((type) => ({ value: type, label: type })),
660
+ onChange: (added) => onChange({ ...selection, entityType: added[0], entities: [] })
661
+ }
662
+ ),
663
+ /* @__PURE__ */ jsx(
664
+ Button,
665
+ {
666
+ icon: "ads_click",
667
+ label: entities.length ? "Change" : "Pick",
668
+ onClick: () => setPicking(true)
669
+ }
670
+ )
671
+ ] }),
672
+ entities.map((entity) => /* @__PURE__ */ jsxs("div", { className: "ayon-shell-entity", children: [
673
+ /* @__PURE__ */ jsx("span", { children: entity.label || entity.name || entity.id }),
674
+ /* @__PURE__ */ jsx(
675
+ Button,
676
+ {
677
+ icon: "close",
678
+ variant: "text",
679
+ "aria-label": "Remove",
680
+ onClick: () => onChange({ ...selection, entities: entities.filter((e) => e.id !== entity.id) })
681
+ }
682
+ )
683
+ ] }, entity.id)),
684
+ picking && // the picker reads the project's folders, like on a project page
685
+ /* @__PURE__ */ jsx(ProjectContextProvider, { projectName, children: /* @__PURE__ */ jsx(ProjectFoldersContextProvider, { projectName, children: /* @__PURE__ */ jsx(
686
+ EntityPickerDialog,
687
+ {
688
+ projectName,
689
+ entityType,
690
+ isMultiSelect: true,
691
+ onClose: () => setPicking(false),
692
+ onSubmit: (ids) => {
693
+ setPicking(false);
694
+ const picked = ids.map((id) => ({ id, entityType, projectName }));
695
+ onChange({ ...selection, entityType, entities: picked });
696
+ }
697
+ }
698
+ ) }) })
699
+ ] });
700
+ };
701
+ const TablePage = ({ selection, onChange }) => /* @__PURE__ */ jsx(Field, { label: "Table", children: /* @__PURE__ */ jsx(
702
+ Dropdown,
703
+ {
704
+ value: [selection.page || "overview"],
705
+ options: ["overview", "products", "lists"].map((page) => ({ value: page, label: page })),
706
+ onChange: (added) => onChange({ ...selection, page: added[0] })
707
+ }
708
+ ) });
709
+ const Fixtures = ({ mode, selection }) => {
710
+ const [open, setOpen] = useState(mode === "preview");
711
+ const [names, setNames] = useState([]);
712
+ const [name, setName] = useState("");
713
+ const [saved, setSaved] = useState();
714
+ const list = () => fetch("/__ayon-addon/fixtures").then((response) => response.ok ? response.json() : []).then(setNames).catch(() => setNames([]));
715
+ useEffect(() => {
716
+ list();
717
+ }, []);
718
+ const save = async () => {
719
+ const fixture2 = window.__AYON_ADDON_SHELL__?.fixture();
720
+ if (!fixture2 || !name) return;
721
+ const response = await fetch(`/__ayon-addon/fixtures/${encodeURIComponent(name)}`, {
722
+ method: "POST",
723
+ headers: { "Content-Type": "application/json" },
724
+ body: JSON.stringify(fixture2, null, 2)
725
+ });
726
+ setSaved(response.ok ? `Saved dev/fixtures/${name}.json` : `Failed: ${response.status}`);
727
+ list();
728
+ };
729
+ return /* @__PURE__ */ jsxs("div", { className: "ayon-shell-fixtures", children: [
730
+ /* @__PURE__ */ jsxs("button", { className: "ayon-shell-subheader", "aria-expanded": open, onClick: () => setOpen(!open), children: [
731
+ /* @__PURE__ */ jsx("span", { className: "label", children: "Preview fixtures" }),
732
+ /* @__PURE__ */ jsx("small", { children: names.length || "" }),
733
+ /* @__PURE__ */ jsx(Icon, { icon: open ? "expand_less" : "expand_more" })
734
+ ] }),
735
+ open && /* @__PURE__ */ jsxs(Fragment, { children: [
736
+ /* @__PURE__ */ jsx("small", { className: "ayon-shell-note", children: "The context and server answers of a contribution, to render it without a server." }),
737
+ mode === "live" && selection.slot && selection.id && /* @__PURE__ */ jsxs("div", { className: "ayon-shell-entities", children: [
738
+ /* @__PURE__ */ jsx(
739
+ InputText,
740
+ {
741
+ value: name,
742
+ placeholder: `${selection.id}`,
743
+ onChange: (e) => setName(e.target.value.replace(/[^\w-]/g, ""))
744
+ }
745
+ ),
746
+ /* @__PURE__ */ jsx(Button, { icon: "save", label: "Save", disabled: !name, onClick: save })
747
+ ] }),
748
+ saved && /* @__PURE__ */ jsx("small", { className: "ayon-shell-note", children: saved }),
749
+ names.map((fixture2) => /* @__PURE__ */ jsxs(
750
+ "a",
751
+ {
752
+ className: `ayon-shell-link${fixture2 === selection.preview ? " selected" : ""}`,
753
+ href: `?preview=${encodeURIComponent(fixture2)}`,
754
+ children: [
755
+ /* @__PURE__ */ jsx(Icon, { icon: "description" }),
756
+ fixture2
757
+ ]
758
+ },
759
+ fixture2
760
+ )),
761
+ selection.preview && /* @__PURE__ */ jsxs("a", { className: "ayon-shell-link", href: "?", children: [
762
+ /* @__PURE__ */ jsx(Icon, { icon: "arrow_back" }),
763
+ "Back to live"
764
+ ] })
765
+ ] })
766
+ ] });
767
+ };
768
+ const LiveStatus = ({ options }) => {
769
+ const { data: user2 } = useGetCurrentUserQuery();
770
+ return /* @__PURE__ */ jsxs("small", { title: `${user2?.name} at ${options.serverUrl}`, children: [
771
+ user2?.name,
772
+ " @ ",
773
+ options.serverUrl.replace(/^https?:\/\//, ""),
774
+ options.signedInWithKey ? " · API key" : "",
775
+ !options.signedInWithKey && /* @__PURE__ */ jsxs(Fragment, { children: [
776
+ " · ",
777
+ /* @__PURE__ */ jsx(
778
+ "a",
779
+ {
780
+ href: "#",
781
+ onClick: (e) => {
782
+ e.preventDefault();
783
+ clearAccessToken();
784
+ window.location.reload();
785
+ },
786
+ children: "sign out"
787
+ }
788
+ )
789
+ ] })
790
+ ] });
791
+ };
792
+ const NotInFixture = () => {
793
+ const unmatched2 = useSyncExternalStore(onUnmatched, () => getUnmatched().join("\n"));
794
+ if (!unmatched2) return null;
795
+ return /* @__PURE__ */ jsxs("div", { className: "ayon-shell-passed", children: [
796
+ /* @__PURE__ */ jsx("div", { className: "ayon-shell-subheader", children: /* @__PURE__ */ jsx("span", { className: "label", children: "Not in the fixture" }) }),
797
+ /* @__PURE__ */ jsx("pre", { className: "ayon-shell-code", children: unmatched2 })
798
+ ] });
799
+ };
800
+ const ContextPreview = () => {
801
+ const context = useSyncExternalStore(subscribeStage, getStageContext);
802
+ const [open, setOpen] = useState(false);
803
+ const text = showContext(context);
804
+ return /* @__PURE__ */ jsxs("div", { className: "ayon-shell-passed", children: [
805
+ /* @__PURE__ */ jsxs("div", { className: "ayon-shell-subheader", children: [
806
+ /* @__PURE__ */ jsx("span", { className: "label", children: "Passed to the contribution" }),
807
+ /* @__PURE__ */ jsx(
808
+ Button,
809
+ {
810
+ icon: "open_in_full",
811
+ variant: "text",
812
+ "data-tooltip": "Show the whole context",
813
+ "aria-label": "Show the whole context",
814
+ disabled: !context,
815
+ onClick: () => setOpen(true)
816
+ }
817
+ )
818
+ ] }),
819
+ /* @__PURE__ */ jsx(
820
+ "button",
821
+ {
822
+ className: "ayon-shell-context",
823
+ "data-shell-context": true,
824
+ title: "Show the whole context",
825
+ disabled: !context,
826
+ onClick: () => setOpen(true),
827
+ children: /* @__PURE__ */ jsx("pre", { className: "ayon-shell-code", children: text })
828
+ }
829
+ ),
830
+ open && context && /* @__PURE__ */ jsx(Dialog, { isOpen: true, header: "Context", size: "lg", onClose: () => setOpen(false), children: /* @__PURE__ */ jsx(JsonViewer, { value: JSON.parse(text), wrap: true }) })
831
+ ] });
832
+ };
833
+ const ContextPanel = ({ mode, selection, onChange }) => {
834
+ const contribution = getRegistry().contributions.find(
835
+ (c) => c.slot === selection.slot && idOf$1(c) === selection.id
836
+ );
837
+ const slot = contribution?.slot;
838
+ const live = mode === "live";
839
+ const pickers = live && !!slot && (needsProject(slot) || needsProjects(slot) || needsEntities(slot));
840
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
841
+ /* @__PURE__ */ jsx("h3", { children: "Context" }),
842
+ !contribution ? /* @__PURE__ */ jsx("small", { className: "ayon-shell-note", children: "Pick a contribution." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
843
+ pickers ? /* @__PURE__ */ jsxs("div", { className: "ayon-shell-fields", children: [
844
+ needsProject(slot) && /* @__PURE__ */ jsx(ProjectPicker, { selection, onChange }),
845
+ needsProjects(slot) && /* @__PURE__ */ jsx(ProjectsPicker, { selection, onChange }),
846
+ needsEntities(slot) && /* @__PURE__ */ jsx(EntitiesPicker, { selection, onChange }),
847
+ needsTablePage(slot) && /* @__PURE__ */ jsx(TablePage, { selection, onChange })
848
+ ] }) : /* @__PURE__ */ jsx("small", { className: "ayon-shell-note", children: live ? "Nothing to pick for this slot." : "From the fixture." }),
849
+ isMenuSlot(slot) && /* @__PURE__ */ jsx(
850
+ Button,
851
+ {
852
+ icon: "play_arrow",
853
+ label: slot === "table.shortcut" ? "Press the shortcut" : "Run the menu item",
854
+ variant: "filled",
855
+ "data-shell-action": "run",
856
+ onClick: () => window.__AYON_ADDON_SHELL__?.run()
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsx(ContextPreview, {}),
860
+ !live && /* @__PURE__ */ jsx(NotInFixture, {})
861
+ ] }),
862
+ /* @__PURE__ */ jsx(Fixtures, { mode, selection })
863
+ ] });
864
+ };
865
+ const DevPanel = ({ options, mode, selection, onChange }) => {
866
+ useEffect(() => {
867
+ if (selection.slot) return;
868
+ const first = getRegistry().contributions[0];
869
+ if (first) onChange({ ...selection, slot: first.slot, id: first.id });
870
+ }, [selection, onChange]);
871
+ return /* @__PURE__ */ jsxs("aside", { className: "ayon-shell-panel", "data-shell-surface": "dev-panel", children: [
872
+ /* @__PURE__ */ jsxs("header", { className: "ayon-shell-panel-header", children: [
873
+ /* @__PURE__ */ jsx(Icon, { icon: "extension" }),
874
+ /* @__PURE__ */ jsxs("div", { children: [
875
+ /* @__PURE__ */ jsxs("strong", { children: [
876
+ options.addon.name,
877
+ " ",
878
+ /* @__PURE__ */ jsx("span", { children: options.addon.version })
879
+ ] }),
880
+ mode === "live" ? /* @__PURE__ */ jsx(LiveStatus, { options }) : /* @__PURE__ */ jsxs("small", { children: [
881
+ "Preview: dev/fixtures/",
882
+ selection.preview,
883
+ ".json, no server"
884
+ ] })
885
+ ] })
886
+ ] }),
887
+ /* @__PURE__ */ jsxs(
888
+ Splitter,
889
+ {
890
+ layout: "vertical",
891
+ className: "ayon-shell-panel-split",
892
+ stateKey: "ayon-addon-shell-panel",
893
+ stateStorage: "local",
894
+ children: [
895
+ /* @__PURE__ */ jsxs(SplitterPanel, { size: 55, minSize: 10, className: "ayon-shell-section", children: [
896
+ /* @__PURE__ */ jsx("h3", { children: "Contributions" }),
897
+ /* @__PURE__ */ jsx(
898
+ Contributions,
899
+ {
900
+ selection,
901
+ onChange: mode === "preview" ? (next) => {
902
+ window.location.href = `?slot=${next.slot}&contribution=${next.id}`;
903
+ } : onChange
904
+ }
905
+ )
906
+ ] }),
907
+ /* @__PURE__ */ jsx(SplitterPanel, { size: 45, minSize: 10, className: "ayon-shell-section", children: /* @__PURE__ */ jsx(ContextPanel, { mode, selection, onChange }) })
908
+ ]
909
+ }
910
+ )
911
+ ] });
912
+ };
913
+ const ButtonItem = ({
914
+ contribution,
915
+ context,
916
+ autoOpen,
917
+ onContext
918
+ }) => {
919
+ const fields = contribution;
920
+ return /* @__PURE__ */ jsx(
921
+ AddonButton,
922
+ {
923
+ contribution,
924
+ context,
925
+ autoOpen,
926
+ onContext,
927
+ renderButton: ({ ref, active, onClick, onMouseEnter }) => /* @__PURE__ */ jsx(
928
+ Button,
929
+ {
930
+ ref,
931
+ icon: fields.icon,
932
+ label: fields.label && !fields.icon ? fields.label : void 0,
933
+ "data-tooltip": fields.tooltip || fields.label,
934
+ variant: "text",
935
+ selected: active,
936
+ "data-contribution-button": contribution.key,
937
+ onClick,
938
+ onMouseEnter
939
+ }
940
+ )
941
+ }
942
+ );
943
+ };
944
+ const ShellTable = ({ page, entities, selected, onSelect }) => {
945
+ const { columns: addonColumns } = useAddonTableColumns(page);
946
+ const columns = useMemo(
947
+ () => [
948
+ {
949
+ id: "name",
950
+ header: "Name",
951
+ size: 260,
952
+ cell: ({ row }) => row.original.primary.label || row.original.primary.name
953
+ },
954
+ ...addonColumns.map((extra) => extra.column)
955
+ ],
956
+ [addonColumns]
957
+ );
958
+ const data = useMemo(
959
+ () => entities.map((entity) => ({
960
+ id: entity.id,
961
+ primary: {
962
+ id: entity.id,
963
+ entityType: entity.entityType,
964
+ name: entity.name || entity.id,
965
+ label: entity.label
966
+ }
967
+ })),
968
+ [entities]
969
+ );
970
+ const table = useReactTable({
971
+ data,
972
+ columns,
973
+ getCoreRowModel: getCoreRowModel(),
974
+ getRowId: (row) => row.id,
975
+ // AYON's table declares its own filter functions (ProjectTreeTable)
976
+ filterFns: {}
977
+ });
978
+ const buildAddonItems = useAddonEntityMenuItems(page);
979
+ const [openMenu] = useCreateContextMenu([]);
980
+ const isSelected = (id) => selected.some((entity) => entity.id === id);
981
+ const onContextMenu = (e, entity) => {
982
+ e.preventDefault();
983
+ const targets = isSelected(entity.id) ? selected : [entity];
984
+ if (!isSelected(entity.id)) onSelect([entity]);
985
+ const cells = targets.map((target) => ({
986
+ cellId: `${target.id}-name`,
987
+ entityId: target.id,
988
+ entityType: target.entityType,
989
+ isGroup: false,
990
+ data: { name: target.name, label: target.label }
991
+ }));
992
+ const cell = cells.find((c) => c.entityId === entity.id);
993
+ const items = buildAddonItems(e, cell, cells, {
994
+ selectedCells: cells.map((c) => c.cellId),
995
+ selectedRows: [],
996
+ selectedColumns: [],
997
+ selectedFullRows: [],
998
+ selectedGroups: []
999
+ }, {});
1000
+ if (Array.isArray(items) && items.length) openMenu(e, items);
1001
+ };
1002
+ return /* @__PURE__ */ jsxs("table", { className: "ayon-shell-table", "data-shell-surface": "table", children: [
1003
+ /* @__PURE__ */ jsx("thead", { children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsx("tr", { children: group.headers.map((header) => /* @__PURE__ */ jsx("th", { style: { width: header.getSize() }, children: flexRender(header.column.columnDef.header, header.getContext()) }, header.id)) }, group.id)) }),
1004
+ /* @__PURE__ */ jsx(
1005
+ AddonColumnDataScope,
1006
+ {
1007
+ columns: table.getAllLeafColumns(),
1008
+ entities: data.map((row) => row.primary),
1009
+ children: /* @__PURE__ */ jsx("tbody", { children: table.getRowModel().rows.map((row) => {
1010
+ const entity = entities.find((e) => e.id === row.id);
1011
+ return /* @__PURE__ */ jsx(
1012
+ "tr",
1013
+ {
1014
+ className: isSelected(row.id) ? "selected" : void 0,
1015
+ onClick: (e) => onSelect(
1016
+ e.metaKey || e.ctrlKey ? isSelected(row.id) ? selected.filter((s) => s.id !== row.id) : [...selected, entity] : [entity]
1017
+ ),
1018
+ onContextMenu: (e) => onContextMenu(e, entity),
1019
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx("td", { className: cell.column.id, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
1020
+ },
1021
+ row.id
1022
+ );
1023
+ }) })
1024
+ }
1025
+ )
1026
+ ] });
1027
+ };
1028
+ const FRAME_WIDTH = {
1029
+ "detailsPanel.tab": 620,
1030
+ "projectTable.customizeSection": 320
1031
+ };
1032
+ const PreviewStage = ({
1033
+ contribution,
1034
+ selection
1035
+ }) => {
1036
+ const fixture2 = getFixture();
1037
+ const context = useMemo(() => fixture2?.context || {}, [fixture2]);
1038
+ const slot = contribution.slot;
1039
+ const run = useCallback(
1040
+ () => runContribution(contribution, context),
1041
+ [contribution, context]
1042
+ );
1043
+ const onPopupContext = useCallback(
1044
+ (popup) => setStage({ context: popup, ready: true }),
1045
+ []
1046
+ );
1047
+ const isPopup = slot === "header.item" || slot === "projectTable.toolbarItem";
1048
+ const isDialog = slot === "table.shortcut" && !!contribution.dialog;
1049
+ const runs = isMenuSlot(slot) && !isDialog;
1050
+ useEffect(() => {
1051
+ if (!fixture2) return;
1052
+ if (!isPopup) setStage({ context, ready: true, run: runs ? run : void 0 });
1053
+ }, [fixture2, context, run, isPopup, runs]);
1054
+ if (!fixture2) {
1055
+ return /* @__PURE__ */ jsxs("div", { role: "alert", className: "ayon-addon-error", children: [
1056
+ "No fixture dev/fixtures/",
1057
+ selection.preview,
1058
+ ".json"
1059
+ ] });
1060
+ }
1061
+ if (fixture2.slot !== slot || fixture2.id !== contribution.id) {
1062
+ return /* @__PURE__ */ jsxs("div", { role: "alert", className: "ayon-addon-error", children: [
1063
+ "The fixture is for ",
1064
+ fixture2.slot,
1065
+ " ",
1066
+ fixture2.id,
1067
+ "."
1068
+ ] });
1069
+ }
1070
+ const frame = (() => {
1071
+ if (isPopup) {
1072
+ return /* @__PURE__ */ jsxs("div", { className: "ayon-shell-bar", children: [
1073
+ /* @__PURE__ */ jsx("span", { className: "spacer" }),
1074
+ /* @__PURE__ */ jsx(ButtonItem, { contribution, context, autoOpen: true, onContext: onPopupContext })
1075
+ ] });
1076
+ }
1077
+ if (isDialog) {
1078
+ return /* @__PURE__ */ jsx("div", { className: "ayon-shell-body", "data-shell-surface": "preview-frame", children: /* @__PURE__ */ jsx(
1079
+ ContributionView,
1080
+ {
1081
+ contribution,
1082
+ piece: "dialog",
1083
+ context: { ...context, close: () => void 0 }
1084
+ }
1085
+ ) });
1086
+ }
1087
+ if (runs) {
1088
+ const item = contribution;
1089
+ return /* @__PURE__ */ jsx("div", { style: { padding: 16 }, children: /* @__PURE__ */ jsx(Button, { icon: item.icon, label: item.label, "data-shell-action": "run", onClick: run }) });
1090
+ }
1091
+ if (slot === "tableColumn") {
1092
+ const entities = context.entities || [];
1093
+ return /* @__PURE__ */ jsx(
1094
+ ShellTable,
1095
+ {
1096
+ page: context.page || "overview",
1097
+ entities,
1098
+ selected: [],
1099
+ onSelect: () => void 0
1100
+ }
1101
+ );
1102
+ }
1103
+ return /* @__PURE__ */ jsx(
1104
+ "div",
1105
+ {
1106
+ className: "ayon-shell-body",
1107
+ style: { width: FRAME_WIDTH[slot] ?? "100%", flex: FRAME_WIDTH[slot] ? "0 0 auto" : 1 },
1108
+ "data-shell-surface": "preview-frame",
1109
+ children: /* @__PURE__ */ jsx(ContributionView, { contribution, context })
1110
+ }
1111
+ );
1112
+ })();
1113
+ return frame;
1114
+ };
1115
+ const DETAILS_SCOPE = "addon-dev";
1116
+ const idOf = (contribution) => contribution.id;
1117
+ const useReport = (contribution, context, ready, run) => {
1118
+ useEffect(() => {
1119
+ setStage({
1120
+ context,
1121
+ ready,
1122
+ run: typeof run === "function" ? run : run && context ? () => runContribution(contribution, context) : void 0
1123
+ });
1124
+ }, [contribution, context, ready, run]);
1125
+ };
1126
+ const Hint = ({ children }) => /* @__PURE__ */ jsx("div", { style: { padding: 16, color: "var(--md-sys-color-outline)" }, children });
1127
+ const ProjectPageBody = ({ contribution, selection }) => {
1128
+ const projectName = selection.projectName;
1129
+ const context = useProjectPageSlotContext(projectName);
1130
+ useReport(contribution, context, !!context.project);
1131
+ return /* @__PURE__ */ jsx("div", { className: "ayon-shell-body", "data-shell-surface": "project-page", children: /* @__PURE__ */ jsx(ContributionView, { contribution, context }) });
1132
+ };
1133
+ const ProjectPageProviders = ({
1134
+ page,
1135
+ projectName,
1136
+ viewType,
1137
+ initialSelection,
1138
+ children
1139
+ }) => {
1140
+ const dispatch = useDispatch();
1141
+ const [rowSelection, setRowSelection] = useState(
1142
+ () => Object.fromEntries((initialSelection || []).map((id) => [id, true]))
1143
+ );
1144
+ const controlled = initialSelection?.length ? { rowSelection, setRowSelection } : {};
1145
+ return /* @__PURE__ */ jsx(ViewsProvider, { viewType, projectName, dispatch, children: /* @__PURE__ */ jsx(EntityUpdatesProvider, { projectNames: [projectName], children: /* @__PURE__ */ jsx(
1146
+ SlicerProvider,
1147
+ {
1148
+ ...controlled,
1149
+ page,
1150
+ projectName,
1151
+ viewSettings: void 0,
1152
+ isLoadingViews: false,
1153
+ updateViewSettings: async () => {
1154
+ },
1155
+ children: /* @__PURE__ */ jsx(NewEntityProvider, { children })
1156
+ }
1157
+ ) }) });
1158
+ };
1159
+ const ProjectPageStage = (props) => /* @__PURE__ */ jsx(
1160
+ ProjectPageProviders,
1161
+ {
1162
+ page: idOf(props.contribution),
1163
+ projectName: props.selection.projectName,
1164
+ viewType: props.contribution.viewType,
1165
+ children: /* @__PURE__ */ jsx(ProjectPageBody, { ...props })
1166
+ },
1167
+ props.contribution.key
1168
+ );
1169
+ const useStudioSelection = (selection, onChange) => {
1170
+ const { projectNames, projectName } = selection;
1171
+ const names = useMemo(
1172
+ () => projectNames ?? (projectName ? [projectName] : []),
1173
+ [projectNames, projectName]
1174
+ );
1175
+ const onSelect = useCallback(
1176
+ (next) => onChange({ ...selection, projectNames: next }),
1177
+ [selection, onChange]
1178
+ );
1179
+ return useMemo(() => ({ selection: names, onSelect }), [names, onSelect]);
1180
+ };
1181
+ const StudioPageBody = ({
1182
+ contribution,
1183
+ selectedProjects
1184
+ }) => {
1185
+ const context = useStudioPageSlotContext(selectedProjects);
1186
+ useReport(contribution, context, true);
1187
+ return /* @__PURE__ */ jsx("div", { className: "ayon-shell-body", "data-shell-surface": "studio-page", children: /* @__PURE__ */ jsx(ContributionView, { contribution, context }) });
1188
+ };
1189
+ const StudioPageStage = ({ contribution, selection, onChange }) => {
1190
+ const dispatch = useDispatch();
1191
+ const studio = useStudioSelection(selection, onChange);
1192
+ return /* @__PURE__ */ jsx(
1193
+ ViewsProvider,
1194
+ {
1195
+ viewType: contribution.viewType,
1196
+ dispatch,
1197
+ children: /* @__PURE__ */ jsx(StudioSelectionProvider, { value: studio, children: /* @__PURE__ */ jsx(StudioPageBody, { contribution, selectedProjects: studio.selection }) })
1198
+ },
1199
+ contribution.key
1200
+ );
1201
+ };
1202
+ const HeaderStage = ({ contribution, selection }) => {
1203
+ const { contributions } = useContributions("header.item");
1204
+ const [context, setContext] = useState();
1205
+ useReport(contribution, context, !!context);
1206
+ const base = useMemo(() => ({ projectName: selection.projectName ?? null }), [selection.projectName]);
1207
+ const sorted = (placement) => contributions.filter((item2) => item2.placement === placement).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
1208
+ const item = (header) => /* @__PURE__ */ jsx(
1209
+ ButtonItem,
1210
+ {
1211
+ contribution: header,
1212
+ context: base,
1213
+ autoOpen: header === contribution,
1214
+ onContext: header === contribution ? setContext : void 0
1215
+ },
1216
+ header.key
1217
+ );
1218
+ return /* @__PURE__ */ jsxs("div", { className: "ayon-shell-bar", "data-shell-surface": "header", children: [
1219
+ /* @__PURE__ */ jsx(Button, { icon: "home", variant: "text", disabled: true }),
1220
+ sorted("left").map(item),
1221
+ /* @__PURE__ */ jsx("span", { className: "spacer" }),
1222
+ sorted("right").map(item),
1223
+ /* @__PURE__ */ jsx(Button, { icon: "forum", variant: "text", disabled: true }),
1224
+ /* @__PURE__ */ jsx(Button, { icon: "inbox", variant: "text", disabled: true })
1225
+ ] });
1226
+ };
1227
+ const DetailsPanelStage = ({ contribution, selection }) => {
1228
+ const projectName = selection.projectName;
1229
+ const { entities, entityType = "task" } = selection;
1230
+ const { data: project } = useGetProjectQuery({ projectName });
1231
+ const { data: users = [] } = useGetUsersAssigneeQuery({ names: void 0, projectName });
1232
+ const { setTab } = useScopedDetailsPanel(DETAILS_SCOPE);
1233
+ const isTab = contribution.slot === "detailsPanel.tab";
1234
+ useEffect(() => {
1235
+ if (isTab) setTab(contribution.key);
1236
+ }, [contribution.key, isTab]);
1237
+ const context = useMemo(
1238
+ () => isTab ? { projectName, entityType, entities, scope: DETAILS_SCOPE } : { projectName, entityType, selectedEntities: entities },
1239
+ [isTab, projectName, entityType, entities]
1240
+ );
1241
+ useReport(contribution, context, !!project && entities.length > 0, !isTab);
1242
+ if (!entities.length) return /* @__PURE__ */ jsx(Hint, { children: "Pick entities in the context panel." });
1243
+ if (!project) return /* @__PURE__ */ jsxs(Hint, { children: [
1244
+ "Loading ",
1245
+ projectName,
1246
+ "…"
1247
+ ] });
1248
+ return /* @__PURE__ */ jsx("div", { className: "ayon-shell-body", style: { justifyContent: "flex-end" }, children: /* @__PURE__ */ jsx("div", { style: { width: "min(620px, 100%)", height: "100%" }, "data-shell-surface": "details-panel", children: /* @__PURE__ */ jsx(
1249
+ DetailsPanel,
1250
+ {
1251
+ isOpen: true,
1252
+ scope: DETAILS_SCOPE,
1253
+ entityType,
1254
+ entities: entities.map((e) => ({ id: e.id, projectName: e.projectName })),
1255
+ projectsInfo: { [projectName]: project },
1256
+ projectNames: [projectName],
1257
+ tagsOptions: project.tags || [],
1258
+ projectUsers: users,
1259
+ activeProjectUsers: users,
1260
+ style: { boxShadow: "none" }
1261
+ }
1262
+ ) }) });
1263
+ };
1264
+ const SlicerBody = ({ contribution, selection }) => {
1265
+ const { rowSelection, sliceType } = useSlicerContext();
1266
+ const context = useMemo(
1267
+ () => ({
1268
+ projectName: selection.projectName,
1269
+ sliceType,
1270
+ selected: Object.keys(rowSelection).filter((id) => rowSelection[id]).map((id) => ({ id })),
1271
+ menu: "context"
1272
+ }),
1273
+ [selection.projectName, sliceType, rowSelection]
1274
+ );
1275
+ useReport(contribution, context, true, true);
1276
+ return (
1277
+ // positioned: the slicer's Section fills its container (a splitter panel in AYON)
1278
+ /* @__PURE__ */ jsx("div", { style: { position: "relative", width: 360, height: "100%" }, "data-shell-surface": "slicer", children: /* @__PURE__ */ jsx(SlicerHost, { fields: ["hierarchy", "assignees", "status", "taskType"] }) })
1279
+ );
1280
+ };
1281
+ const SlicerStage = (props) => /* @__PURE__ */ jsx("div", { className: "ayon-shell-body", children: /* @__PURE__ */ jsx(
1282
+ ProjectPageProviders,
1283
+ {
1284
+ page: `addon-dev-${idOf(props.contribution)}`,
1285
+ projectName: props.selection.projectName,
1286
+ initialSelection: props.selection.entities.map((entity) => entity.id),
1287
+ children: /* @__PURE__ */ jsx(SlicerBody, { ...props })
1288
+ }
1289
+ ) });
1290
+ const ProjectListStage = ({ contribution, selection, onChange }) => {
1291
+ const studio = useStudioSelection(selection, onChange);
1292
+ const context = useMemo(() => ({ projectNames: studio.selection, menu: "context" }), [studio.selection]);
1293
+ useReport(contribution, context, true, true);
1294
+ const { ProjectList: ProjectList2 } = getHostComponents();
1295
+ return /* @__PURE__ */ jsx(StudioSelectionProvider, { value: studio, children: /* @__PURE__ */ jsx("div", { style: { width: 280, height: "100%" }, children: /* @__PURE__ */ jsx(ProjectList2, { multiSelect: true }) }) });
1296
+ };
1297
+ const TableStage = ({ contribution, selection }) => {
1298
+ const projectName = selection.projectName;
1299
+ const page = selection.page || "overview";
1300
+ const { entities } = selection;
1301
+ const [selected, setSelected] = useState([]);
1302
+ const [popupContext, setPopupContext] = useState();
1303
+ const { contributions: toolbarItems } = useContributions("projectTable.toolbarItem");
1304
+ const sections = useAddonCustomizeSections(page);
1305
+ const { isPanelOpen, openPanel, selectSetting, closePanel } = useSettingsPanel();
1306
+ const slot = contribution.slot;
1307
+ useEffect(() => {
1308
+ if (slot !== "projectTable.customizeSection") return;
1309
+ openPanel();
1310
+ selectSetting(`addon:${contribution.key}`);
1311
+ }, [contribution.key, slot]);
1312
+ const shortcuts = useAddonTableShortcuts(slot === "table.shortcut" ? page : void 0);
1313
+ const shortcutContext = useMemo(() => {
1314
+ const targets = selected.length ? selected : entities;
1315
+ if (slot !== "table.shortcut" || !targets.length) return void 0;
1316
+ const rows = targets.map(({ id, entityType, name, label }) => ({
1317
+ id,
1318
+ entityType,
1319
+ name,
1320
+ label,
1321
+ data: { id, entityType, name, label }
1322
+ }));
1323
+ return {
1324
+ table: page,
1325
+ projectName,
1326
+ rows,
1327
+ focused: { rowId: rows[0].id, columnId: "name" },
1328
+ cells: rows.map((row) => ({ rowId: row.id, columnId: "name" }))
1329
+ };
1330
+ }, [slot, page, projectName, entities, selected]);
1331
+ const { runShortcut } = shortcuts;
1332
+ const pressShortcut = useMemo(
1333
+ () => shortcutContext && (async () => runShortcut(contribution, shortcutContext)),
1334
+ [runShortcut, contribution, shortcutContext]
1335
+ );
1336
+ const context = useMemo(() => {
1337
+ if (slot === "tableColumn") return { projectName, page, entities };
1338
+ if (slot === "entityMenuItem")
1339
+ return { projectName, page, entities: selected.length ? selected : entities };
1340
+ if (slot === "table.shortcut") return shortcutContext;
1341
+ if (slot === "projectTable.customizeSection") return { projectName, page };
1342
+ return popupContext;
1343
+ }, [slot, projectName, page, entities, selected, shortcutContext, popupContext]);
1344
+ useReport(contribution, context, !!context, slot === "entityMenuItem" || pressShortcut);
1345
+ const base = useMemo(() => ({ projectName, page }), [projectName, page]);
1346
+ const pageToolbarItems = toolbarItems.filter((item) => !item.pages?.length || item.pages.includes(page));
1347
+ return /* @__PURE__ */ jsxs("div", { className: "ayon-shell-stage", style: { flex: 1 }, children: [
1348
+ /* @__PURE__ */ jsxs("div", { className: "ayon-shell-bar", "data-shell-surface": "table-toolbar", children: [
1349
+ /* @__PURE__ */ jsx("span", { children: page }),
1350
+ /* @__PURE__ */ jsx("span", { className: "spacer" }),
1351
+ pageToolbarItems.map((item) => /* @__PURE__ */ jsx(
1352
+ ButtonItem,
1353
+ {
1354
+ contribution: item,
1355
+ context: base,
1356
+ autoOpen: item === contribution,
1357
+ onContext: item === contribution ? setPopupContext : void 0
1358
+ },
1359
+ item.key
1360
+ )),
1361
+ /* @__PURE__ */ jsx(
1362
+ Button,
1363
+ {
1364
+ icon: "settings",
1365
+ label: "Customize",
1366
+ variant: "text",
1367
+ selected: isPanelOpen,
1368
+ onClick: () => isPanelOpen ? closePanel() : openPanel()
1369
+ }
1370
+ )
1371
+ ] }),
1372
+ /* @__PURE__ */ jsxs("div", { className: "ayon-shell-body", children: [
1373
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto" }, children: entities.length ? /* @__PURE__ */ jsx(
1374
+ "div",
1375
+ {
1376
+ tabIndex: 0,
1377
+ onKeyDown: (e) => shortcuts.handleShortcut(e, () => shortcutContext),
1378
+ children: /* @__PURE__ */ jsx(
1379
+ ShellTable,
1380
+ {
1381
+ page,
1382
+ entities,
1383
+ selected,
1384
+ onSelect: setSelected
1385
+ }
1386
+ )
1387
+ }
1388
+ ) : /* @__PURE__ */ jsx(Hint, { children: "Pick entities in the context panel: they are the table's rows." }) }),
1389
+ isPanelOpen && /* @__PURE__ */ jsx(
1390
+ "div",
1391
+ {
1392
+ style: { width: 320, borderLeft: "1px solid var(--md-sys-color-outline-variant)" },
1393
+ "data-shell-surface": "customize",
1394
+ children: /* @__PURE__ */ jsx(SettingsPanel, { settings: sections })
1395
+ }
1396
+ )
1397
+ ] }),
1398
+ shortcuts.dialog && /* @__PURE__ */ jsx(AddonDialog, { contribution: shortcuts.dialog.contribution, context: shortcuts.dialog.context })
1399
+ ] });
1400
+ };
1401
+ const getHostComponents = () => getHost().components;
1402
+ const Stage = ({
1403
+ mode,
1404
+ selection,
1405
+ onChange
1406
+ }) => {
1407
+ const contribution = getRegistry().contributions.find(
1408
+ (c) => c.slot === selection.slot && idOf(c) === selection.id
1409
+ );
1410
+ useEffect(() => {
1411
+ if (!contribution) setStage({ ready: false });
1412
+ }, [contribution]);
1413
+ if (!contribution) {
1414
+ return /* @__PURE__ */ jsx("main", { className: "ayon-shell-stage", children: /* @__PURE__ */ jsx(Hint, { children: selection.slot ? `No contribution "${selection.id}" in ${selection.slot}.` : "Pick a contribution." }) });
1415
+ }
1416
+ if (mode === "preview") {
1417
+ return /* @__PURE__ */ jsx("main", { className: "ayon-shell-stage", children: /* @__PURE__ */ jsx(PreviewStage, { contribution, selection }) });
1418
+ }
1419
+ if (!selection.projectName && needsProject(contribution.slot)) {
1420
+ return /* @__PURE__ */ jsx("main", { className: "ayon-shell-stage", children: /* @__PURE__ */ jsx(Hint, { children: "Pick a project in the context panel." }) });
1421
+ }
1422
+ const props = { contribution, selection };
1423
+ const body = (() => {
1424
+ switch (contribution.slot) {
1425
+ case "project.page":
1426
+ return /* @__PURE__ */ jsx(ProjectPageStage, { ...props });
1427
+ case "studio.page":
1428
+ return /* @__PURE__ */ jsx(StudioPageStage, { ...props, onChange });
1429
+ case "header.item":
1430
+ return /* @__PURE__ */ jsx(HeaderStage, { ...props });
1431
+ case "detailsPanel.tab":
1432
+ case "detailsPanel.menuItem":
1433
+ return /* @__PURE__ */ jsx(DetailsPanelStage, { ...props });
1434
+ case "slicer.menuItem":
1435
+ return /* @__PURE__ */ jsx(SlicerStage, { ...props });
1436
+ case "projectList.menuItem":
1437
+ return /* @__PURE__ */ jsx(ProjectListStage, { ...props, onChange });
1438
+ case "tableColumn":
1439
+ case "entityMenuItem":
1440
+ case "table.shortcut":
1441
+ case "projectTable.toolbarItem":
1442
+ case "projectTable.customizeSection":
1443
+ return /* @__PURE__ */ jsx(TableStage, { ...props });
1444
+ default:
1445
+ return /* @__PURE__ */ jsxs(Hint, { children: [
1446
+ "The shell has no stage for ",
1447
+ contribution.slot,
1448
+ " yet."
1449
+ ] });
1450
+ }
1451
+ })();
1452
+ return /* @__PURE__ */ jsx("main", { className: "ayon-shell-stage", "data-shell-slot": contribution.slot, children: body });
1453
+ };
1454
+ const StageCaption = ({
1455
+ mode,
1456
+ selection
1457
+ }) => {
1458
+ const contribution = getRegistry().contributions.find(
1459
+ (c) => c.slot === selection.slot && c.id === selection.id
1460
+ );
1461
+ if (!contribution) return null;
1462
+ const slot = contribution.slot;
1463
+ return /* @__PURE__ */ jsxs("header", { className: "ayon-shell-caption", "data-shell-surface": "caption", children: [
1464
+ /* @__PURE__ */ jsx(Icon, { icon: slotIcon(slot) }),
1465
+ /* @__PURE__ */ jsxs("div", { className: "ayon-shell-caption-text", children: [
1466
+ /* @__PURE__ */ jsxs("div", { children: [
1467
+ /* @__PURE__ */ jsx("strong", { children: contributionTitle(contribution) }),
1468
+ /* @__PURE__ */ jsx("span", { className: "ayon-shell-caption-path", children: slotPath(slot).map((part) => /* @__PURE__ */ jsx("span", { children: part }, part)) })
1469
+ ] }),
1470
+ /* @__PURE__ */ jsxs("small", { children: [
1471
+ SLOT_DOCS[slot]?.where,
1472
+ " · ",
1473
+ /* @__PURE__ */ jsx("code", { children: slot })
1474
+ ] })
1475
+ ] }),
1476
+ /* @__PURE__ */ jsx("span", { className: `ayon-shell-chip ${mode}`, children: mode === "live" ? "Live" : `Preview: ${selection.preview}` })
1477
+ ] });
1478
+ };
1479
+ class StageBoundary extends Component {
1480
+ state = { error: null };
1481
+ static getDerivedStateFromError(error) {
1482
+ return { error };
1483
+ }
1484
+ componentDidCatch(error, info) {
1485
+ const what = this.props.what || "stage";
1486
+ console.error(`[ayon-addon shell] the ${what} crashed`, error, info.componentStack);
1487
+ if (what === "stage") setStage({ ready: false });
1488
+ }
1489
+ render() {
1490
+ if (!this.state.error) return this.props.children;
1491
+ const what = this.props.what || "stage";
1492
+ const message = /* @__PURE__ */ jsxs("div", { role: "alert", className: "ayon-addon-error ayon-shell-crash", style: { margin: 16 }, children: [
1493
+ "The dev shell's ",
1494
+ what,
1495
+ " crashed: ",
1496
+ this.state.error.message
1497
+ ] });
1498
+ return what === "stage" ? /* @__PURE__ */ jsx("main", { className: "ayon-shell-stage", children: message }) : /* @__PURE__ */ jsx("aside", { children: message });
1499
+ }
1500
+ }
1501
+ const Centered = ({ children }) => /* @__PURE__ */ jsx("div", { style: { display: "grid", placeItems: "center", height: "100%" }, children });
1502
+ const SignedIn = ({ options, children }) => {
1503
+ const { data: user2, error, isLoading, refetch } = useGetCurrentUserQuery();
1504
+ if (isLoading) return /* @__PURE__ */ jsxs(Centered, { children: [
1505
+ "Connecting to ",
1506
+ options.serverUrl,
1507
+ "…"
1508
+ ] });
1509
+ if (!user2) {
1510
+ const status = error?.status;
1511
+ if (status === 401 || status === 403) {
1512
+ return /* @__PURE__ */ jsx(
1513
+ LoginPage,
1514
+ {
1515
+ onLogin: (result) => {
1516
+ if (result.accessToken) storeAccessToken(result.accessToken);
1517
+ refetch();
1518
+ }
1519
+ }
1520
+ );
1521
+ }
1522
+ return /* @__PURE__ */ jsx(Centered, { children: /* @__PURE__ */ jsxs("div", { role: "alert", className: "ayon-addon-error", children: [
1523
+ "Can't reach AYON at ",
1524
+ options.serverUrl,
1525
+ " (",
1526
+ status ?? "no answer",
1527
+ "). Set AYON_SERVER_URL."
1528
+ ] }) });
1529
+ }
1530
+ return /* @__PURE__ */ jsx(Fragment, { children });
1531
+ };
1532
+ const AppProviders = ({ children }) => {
1533
+ const dispatch = useDispatch();
1534
+ const { data: user2 } = useGetCurrentUserQuery();
1535
+ return /* @__PURE__ */ jsx(GlobalProvider, { children: /* @__PURE__ */ jsx(PowerpackProvider, { children: /* @__PURE__ */ jsxs(ContextMenuProvider, { children: [
1536
+ /* @__PURE__ */ jsx(GlobalContextMenu, {}),
1537
+ /* @__PURE__ */ jsx(MenuProvider, { useNavigate, children: /* @__PURE__ */ jsx(URIProvider, { children: /* @__PURE__ */ jsx(
1538
+ DetailsPanelProvider,
1539
+ {
1540
+ user: user2,
1541
+ dispatch,
1542
+ useLocation,
1543
+ useNavigate,
1544
+ useParams,
1545
+ useSearchParams,
1546
+ children: /* @__PURE__ */ jsx(PiPProvider, { children: /* @__PURE__ */ jsx(DeleteEntitiesProvider, { children: /* @__PURE__ */ jsx(SettingsPanelProvider, { children }) }) })
1547
+ }
1548
+ ) }) })
1549
+ ] }) }) });
1550
+ };
1551
+ const ProjectProviders = ({
1552
+ projectName,
1553
+ light,
1554
+ children
1555
+ }) => projectName && light ? /* @__PURE__ */ jsx(ProjectContextProvider, { projectName, children }) : projectName ? /* @__PURE__ */ jsx(ProjectContextProvider, { projectName, children: /* @__PURE__ */ jsx(ProjectFoldersContextProvider, { projectName, children: /* @__PURE__ */ jsx(ProjectDataProvider, { projectName, children }) }) }) : /* @__PURE__ */ jsx(Fragment, { children });
1556
+ const Workbench = ({ options, mode }) => {
1557
+ const [selection, setSelection] = useState(readSelection);
1558
+ useEffect(() => writeSelection(selection), [selection]);
1559
+ useEffect(() => setStatus({ selection, mode }), [selection, mode]);
1560
+ return /* @__PURE__ */ jsxs(Splitter, { className: "ayon-shell", stateKey: "ayon-addon-shell", stateStorage: "local", children: [
1561
+ /* @__PURE__ */ jsx(SplitterPanel, { size: 22, minSize: 12, style: { overflow: "hidden" }, children: /* @__PURE__ */ jsx(StageBoundary, { what: "developer panel", children: /* @__PURE__ */ jsx(DevPanel, { options, mode, selection, onChange: setSelection }) }) }),
1562
+ /* @__PURE__ */ jsxs(SplitterPanel, { size: 78, style: { overflow: "hidden", display: "flex", flexDirection: "column" }, children: [
1563
+ /* @__PURE__ */ jsx(StageCaption, { mode, selection }),
1564
+ /* @__PURE__ */ jsx(
1565
+ ProjectProviders,
1566
+ {
1567
+ projectName: mode === "live" || selection.slot === "tableColumn" ? selection.projectName : void 0,
1568
+ light: mode === "preview",
1569
+ children: /* @__PURE__ */ jsx(StageBoundary, { children: /* @__PURE__ */ jsx(
1570
+ Stage,
1571
+ {
1572
+ mode,
1573
+ selection,
1574
+ onChange: setSelection
1575
+ },
1576
+ selection.projectName || ""
1577
+ ) }, `${selection.projectName}:${selection.slot}:${selection.id}`)
1578
+ }
1579
+ )
1580
+ ] })
1581
+ ] });
1582
+ };
1583
+ const Shell = ({ options, mode }) => {
1584
+ const store = useMemo(() => {
1585
+ const standaloneStore = createStandaloneStore();
1586
+ provideHost({ dispatch: standaloneStore.dispatch });
1587
+ return standaloneStore;
1588
+ }, []);
1589
+ return /* @__PURE__ */ jsxs(Provider, { store, children: [
1590
+ /* @__PURE__ */ jsx(SplitterStyles, {}),
1591
+ /* @__PURE__ */ jsxs(BrowserRouter, { children: [
1592
+ mode === "live" ? /* @__PURE__ */ jsx(SignedIn, { options, children: /* @__PURE__ */ jsx(AppProviders, { children: /* @__PURE__ */ jsx(Workbench, { options, mode }) }) }) : /* @__PURE__ */ jsx(AppProviders, { children: /* @__PURE__ */ jsx(Workbench, { options, mode }) }),
1593
+ /* @__PURE__ */ jsx(ToastContainer, { position: "bottom-right", theme: "dark" })
1594
+ ] })
1595
+ ] });
1596
+ };
1597
+ const FIXTURES_PATH = "/__ayon-addon/fixtures";
1598
+ const loadFixture = async (name) => {
1599
+ const response = await fetch(`${FIXTURES_PATH}/${encodeURIComponent(name)}`);
1600
+ if (!response.ok) return void 0;
1601
+ const fixture2 = await response.json();
1602
+ fixture2.responses ??= {};
1603
+ if (fixture2.user) fixture2.responses["GET /api/users/me"] ??= { status: 200, body: fixture2.user };
1604
+ return fixture2;
1605
+ };
1606
+ const startShell = async (options) => {
1607
+ const preview = readSelection().preview;
1608
+ const mode = preview ? "preview" : "live";
1609
+ installNetwork(mode);
1610
+ provideShellHost();
1611
+ registerManifest(options.addon, options.manifest);
1612
+ setManifestsLoading(false);
1613
+ if (preview) {
1614
+ const fixture2 = await loadFixture(preview);
1615
+ setFixture(fixture2);
1616
+ if (fixture2) {
1617
+ const context = fixture2.context;
1618
+ writeSelection({
1619
+ preview,
1620
+ slot: fixture2.slot,
1621
+ id: fixture2.id,
1622
+ projectName: context.projectName,
1623
+ entities: context.entities || [],
1624
+ page: context.page
1625
+ });
1626
+ }
1627
+ }
1628
+ createRoot(document.getElementById("root")).render(
1629
+ /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(Shell, { options, mode }) })
1630
+ );
1631
+ };
1632
+ export {
1633
+ FIXTURES_PATH,
1634
+ startShell
1635
+ };