@flytedan/flytebot-design-system 0.14.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/README.md +44 -0
- package/dist/index.cjs +1665 -1292
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +481 -207
- package/dist/index.d.ts +481 -207
- package/dist/index.js +1667 -1307
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/styles/tokens.css +44 -0
package/dist/index.js
CHANGED
|
@@ -5137,8 +5137,835 @@ var MarkdownEditor = React29.forwardRef(function MarkdownEditor2({
|
|
|
5137
5137
|
// src/components/platform/AccountMenu.tsx
|
|
5138
5138
|
import * as React31 from "react";
|
|
5139
5139
|
|
|
5140
|
-
// src/kits/
|
|
5140
|
+
// src/kits/sessionContext.tsx
|
|
5141
5141
|
import * as React30 from "react";
|
|
5142
|
+
var SessionContext = React30.createContext(null);
|
|
5143
|
+
function SessionProvider({ value, children }) {
|
|
5144
|
+
return React30.createElement(SessionContext.Provider, { value }, children);
|
|
5145
|
+
}
|
|
5146
|
+
function useSessionContext() {
|
|
5147
|
+
const ctx = React30.useContext(SessionContext);
|
|
5148
|
+
if (!ctx) {
|
|
5149
|
+
throw new Error(
|
|
5150
|
+
"useSessionContext() was called outside a <SessionProvider>. Wrap your app root in <SessionProvider value={...}> with your real session, or \u2014 in the docs site or a demo \u2014 in <DemoSessionProvider>, before rendering AccountMenu, Gate, ProfilePage or AppSwitcher."
|
|
5151
|
+
);
|
|
5152
|
+
}
|
|
5153
|
+
return ctx;
|
|
5154
|
+
}
|
|
5155
|
+
|
|
5156
|
+
// src/components/platform/AccountMenu.tsx
|
|
5157
|
+
import { Fragment as Fragment14, jsx as jsx57, jsxs as jsxs53 } from "react/jsx-runtime";
|
|
5158
|
+
var DEFAULT_LINKS = [
|
|
5159
|
+
{ id: "profile", label: "Your profile", icon: "user-circle", href: "../admin/index.html#profile" },
|
|
5160
|
+
{ id: "preferences", label: "Preferences", icon: "sliders-horizontal", href: "../admin/index.html#preferences" }
|
|
5161
|
+
];
|
|
5162
|
+
var DEFAULT_GROUPS = [
|
|
5163
|
+
{
|
|
5164
|
+
label: "Administration",
|
|
5165
|
+
links: [
|
|
5166
|
+
{ id: "users", label: "Users", icon: "users-three", href: "../admin/index.html#users", perm: "users.view" },
|
|
5167
|
+
{ id: "roles", label: "Roles & permissions", icon: "shield-check", href: "../admin/index.html#roles", perm: "roles.manage" },
|
|
5168
|
+
{ id: "flags", label: "Feature flags", icon: "toggle-right", href: "../admin/index.html#flags", perm: "flags.manage" }
|
|
5169
|
+
]
|
|
5170
|
+
}
|
|
5171
|
+
];
|
|
5172
|
+
function Item({ item, onPick }) {
|
|
5173
|
+
return /* @__PURE__ */ jsxs53(
|
|
5174
|
+
"button",
|
|
5175
|
+
{
|
|
5176
|
+
type: "button",
|
|
5177
|
+
role: "menuitem",
|
|
5178
|
+
className: "fd-acct-item",
|
|
5179
|
+
onClick: () => onPick(item),
|
|
5180
|
+
children: [
|
|
5181
|
+
item.icon ? /* @__PURE__ */ jsx57("i", { className: "ph ph-" + item.icon, "aria-hidden": "true" }) : null,
|
|
5182
|
+
/* @__PURE__ */ jsx57("span", { style: { flex: 1 }, children: item.label }),
|
|
5183
|
+
item.badge ? /* @__PURE__ */ jsx57(Badge, { tone: "neutral", children: item.badge }) : null
|
|
5184
|
+
]
|
|
5185
|
+
}
|
|
5186
|
+
);
|
|
5187
|
+
}
|
|
5188
|
+
function AccountMenu({
|
|
5189
|
+
links = DEFAULT_LINKS,
|
|
5190
|
+
groups = DEFAULT_GROUPS,
|
|
5191
|
+
onNavigate,
|
|
5192
|
+
onSignOut,
|
|
5193
|
+
showRoles = true,
|
|
5194
|
+
allowUserSwitch = true,
|
|
5195
|
+
className = "",
|
|
5196
|
+
...rest
|
|
5197
|
+
}) {
|
|
5198
|
+
const { user, roles, can: can2, signOut: ctxSignOut, demo } = useSessionContext();
|
|
5199
|
+
const [open, setOpen] = React31.useState(false);
|
|
5200
|
+
const [switching, setSwitching] = React31.useState(false);
|
|
5201
|
+
const [signingOut, setSigningOut] = React31.useState(false);
|
|
5202
|
+
const ref = React31.useRef(null);
|
|
5203
|
+
const visibleLinks = links.filter((l) => !l.perm || can2(l.perm));
|
|
5204
|
+
const visibleGroups = groups.map((g) => ({ label: g.label, links: g.links.filter((l) => !l.perm || can2(l.perm)) })).filter((g) => g.links.length);
|
|
5205
|
+
const pick = (item) => {
|
|
5206
|
+
setOpen(false);
|
|
5207
|
+
if (item.onSelect) return item.onSelect();
|
|
5208
|
+
if (onNavigate) return onNavigate(item.href, item);
|
|
5209
|
+
if (item.href) window.location.href = item.href;
|
|
5210
|
+
};
|
|
5211
|
+
const signOut = async () => {
|
|
5212
|
+
const handler = onSignOut ?? ctxSignOut;
|
|
5213
|
+
setSigningOut(true);
|
|
5214
|
+
try {
|
|
5215
|
+
await handler();
|
|
5216
|
+
} catch (err) {
|
|
5217
|
+
console.error("AccountMenu: sign-out handler failed", err);
|
|
5218
|
+
} finally {
|
|
5219
|
+
setSigningOut(false);
|
|
5220
|
+
setOpen(false);
|
|
5221
|
+
}
|
|
5222
|
+
};
|
|
5223
|
+
const showSwitcher = allowUserSwitch && !!demo;
|
|
5224
|
+
return /* @__PURE__ */ jsxs53(Fragment14, { children: [
|
|
5225
|
+
/* @__PURE__ */ jsxs53(
|
|
5226
|
+
"button",
|
|
5227
|
+
{
|
|
5228
|
+
type: "button",
|
|
5229
|
+
ref,
|
|
5230
|
+
className: ["fd-acct-trigger", className].filter(Boolean).join(" "),
|
|
5231
|
+
"aria-haspopup": "menu",
|
|
5232
|
+
"aria-expanded": open,
|
|
5233
|
+
"aria-label": "Account \u2014 " + user.name,
|
|
5234
|
+
onClick: () => setOpen((o) => !o),
|
|
5235
|
+
...rest,
|
|
5236
|
+
children: [
|
|
5237
|
+
/* @__PURE__ */ jsx57(Avatar, { name: user.name, size: "sm" }),
|
|
5238
|
+
/* @__PURE__ */ jsx57("i", { className: "ph ph-caret-down", "aria-hidden": "true", style: { fontSize: 11, color: "var(--text-muted)" } })
|
|
5239
|
+
]
|
|
5240
|
+
}
|
|
5241
|
+
),
|
|
5242
|
+
/* @__PURE__ */ jsx57(Popover, { open, anchorRef: ref, onClose: () => setOpen(false), placement: "bottom-end", width: 272, children: /* @__PURE__ */ jsxs53("div", { role: "menu", className: "fd-stack", style: { gap: 0 }, children: [
|
|
5243
|
+
/* @__PURE__ */ jsxs53("div", { className: "fd-row", style: { gap: 10, padding: "12px 14px", borderBottom: "1px solid var(--border)" }, children: [
|
|
5244
|
+
/* @__PURE__ */ jsx57(Avatar, { name: user.name }),
|
|
5245
|
+
/* @__PURE__ */ jsxs53("span", { className: "fd-stack", style: { gap: 1, minWidth: 0, flex: 1 }, children: [
|
|
5246
|
+
/* @__PURE__ */ jsx57("span", { className: "fd-body-sm", style: { fontWeight: 700 }, children: user.name }),
|
|
5247
|
+
/* @__PURE__ */ jsx57("span", { className: "fd-body-sm fd-muted fd-table-trunc", title: truncationTitle(user.email), children: user.email })
|
|
5248
|
+
] })
|
|
5249
|
+
] }),
|
|
5250
|
+
showRoles && roles.length ? /* @__PURE__ */ jsx57("div", { className: "fd-row", style: { gap: 6, padding: "10px 14px", flexWrap: "wrap", borderBottom: "1px solid var(--border)" }, children: roles.map((r) => /* @__PURE__ */ jsx57(Badge, { tone: r.id === "owner" ? "success" : "neutral", children: r.name }, r.id)) }) : null,
|
|
5251
|
+
visibleLinks.length ? /* @__PURE__ */ jsx57("div", { className: "fd-acct-group", children: visibleLinks.map((l) => /* @__PURE__ */ jsx57(Item, { item: l, onPick: pick }, l.id)) }) : null,
|
|
5252
|
+
visibleGroups.map((g) => /* @__PURE__ */ jsxs53("div", { className: "fd-acct-group", style: { borderTop: "1px solid var(--border)" }, children: [
|
|
5253
|
+
/* @__PURE__ */ jsx57("span", { className: "fd-overline fd-muted", style: { padding: "8px 14px 4px", display: "block" }, children: g.label }),
|
|
5254
|
+
g.links.map((l) => /* @__PURE__ */ jsx57(Item, { item: l, onPick: pick }, l.id))
|
|
5255
|
+
] }, g.label)),
|
|
5256
|
+
showSwitcher && demo ? /* @__PURE__ */ jsxs53("div", { className: "fd-acct-group", style: { borderTop: "1px solid var(--border)" }, children: [
|
|
5257
|
+
/* @__PURE__ */ jsxs53("button", { type: "button", role: "menuitem", className: "fd-acct-item", onClick: () => setSwitching((s) => !s), children: [
|
|
5258
|
+
/* @__PURE__ */ jsx57("i", { className: "ph ph-user-switch", "aria-hidden": "true" }),
|
|
5259
|
+
/* @__PURE__ */ jsx57("span", { style: { flex: 1 }, children: "View as another member" }),
|
|
5260
|
+
/* @__PURE__ */ jsx57("i", { className: "ph ph-caret-" + (switching ? "up" : "down"), "aria-hidden": "true", style: { fontSize: 11 } })
|
|
5261
|
+
] }),
|
|
5262
|
+
switching ? /* @__PURE__ */ jsx57("div", { className: "fd-stack", style: { gap: 0 }, children: demo.allUsers.map((u) => /* @__PURE__ */ jsxs53(
|
|
5263
|
+
"button",
|
|
5264
|
+
{
|
|
5265
|
+
type: "button",
|
|
5266
|
+
className: "fd-acct-item",
|
|
5267
|
+
"data-current": u.id === user.id ? "true" : "false",
|
|
5268
|
+
onClick: () => {
|
|
5269
|
+
demo.setActiveUser(u.id);
|
|
5270
|
+
setOpen(false);
|
|
5271
|
+
setSwitching(false);
|
|
5272
|
+
},
|
|
5273
|
+
children: [
|
|
5274
|
+
/* @__PURE__ */ jsx57(Avatar, { name: u.name, size: "sm" }),
|
|
5275
|
+
/* @__PURE__ */ jsxs53("span", { className: "fd-stack", style: { gap: 0, flex: 1, minWidth: 0, alignItems: "flex-start" }, children: [
|
|
5276
|
+
/* @__PURE__ */ jsx57("span", { style: { fontWeight: u.id === user.id ? 700 : 500 }, children: u.name }),
|
|
5277
|
+
u.roles ? /* @__PURE__ */ jsx57("span", { className: "fd-muted", style: { fontSize: 11.5 }, children: u.roles.join(", ") }) : null
|
|
5278
|
+
] }),
|
|
5279
|
+
u.id === user.id ? /* @__PURE__ */ jsx57("i", { className: "ph ph-check", "aria-hidden": "true", style: { color: "var(--ok-text)" } }) : null
|
|
5280
|
+
]
|
|
5281
|
+
},
|
|
5282
|
+
u.id
|
|
5283
|
+
)) }) : null
|
|
5284
|
+
] }) : null,
|
|
5285
|
+
/* @__PURE__ */ jsx57("div", { className: "fd-acct-group", style: { borderTop: "1px solid var(--border)" }, children: /* @__PURE__ */ jsxs53(
|
|
5286
|
+
"button",
|
|
5287
|
+
{
|
|
5288
|
+
type: "button",
|
|
5289
|
+
role: "menuitem",
|
|
5290
|
+
className: "fd-acct-item",
|
|
5291
|
+
"data-danger": "true",
|
|
5292
|
+
disabled: signingOut,
|
|
5293
|
+
onClick: signOut,
|
|
5294
|
+
children: [
|
|
5295
|
+
signingOut ? /* @__PURE__ */ jsx57(Spinner, { size: "sm" }) : /* @__PURE__ */ jsx57("i", { className: "ph ph-sign-out", "aria-hidden": "true" }),
|
|
5296
|
+
/* @__PURE__ */ jsx57("span", { style: { flex: 1 }, children: signingOut ? "Signing out\u2026" : "Sign out" })
|
|
5297
|
+
]
|
|
5298
|
+
}
|
|
5299
|
+
) })
|
|
5300
|
+
] }) })
|
|
5301
|
+
] });
|
|
5302
|
+
}
|
|
5303
|
+
|
|
5304
|
+
// src/components/platform/AppSwitcher.tsx
|
|
5305
|
+
import * as React32 from "react";
|
|
5306
|
+
import { jsx as jsx58, jsxs as jsxs54 } from "react/jsx-runtime";
|
|
5307
|
+
function AppSwitcher({ apps: appsProp, onNavigate, className = "" }) {
|
|
5308
|
+
const { apps: contextApps } = useSessionContext();
|
|
5309
|
+
const apps = appsProp ?? contextApps ?? [];
|
|
5310
|
+
const [open, setOpen] = React32.useState(false);
|
|
5311
|
+
const anchorRef = React32.useRef(null);
|
|
5312
|
+
if (!apps.length) return null;
|
|
5313
|
+
const items = apps.map((app) => ({
|
|
5314
|
+
id: app.key,
|
|
5315
|
+
label: app.name,
|
|
5316
|
+
icon: app.icon,
|
|
5317
|
+
meta: app.current ? "Current" : void 0,
|
|
5318
|
+
disabled: app.current,
|
|
5319
|
+
onSelect: () => {
|
|
5320
|
+
if (app.current) return;
|
|
5321
|
+
if (onNavigate) return onNavigate(app);
|
|
5322
|
+
window.location.href = app.url;
|
|
5323
|
+
}
|
|
5324
|
+
}));
|
|
5325
|
+
return /* @__PURE__ */ jsxs54(React32.Fragment, { children: [
|
|
5326
|
+
/* @__PURE__ */ jsx58("span", { ref: anchorRef, className: ["fd-appswitch-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsx58(
|
|
5327
|
+
IconButton,
|
|
5328
|
+
{
|
|
5329
|
+
icon: "squares-four",
|
|
5330
|
+
label: "All flytedesk apps",
|
|
5331
|
+
"aria-haspopup": "menu",
|
|
5332
|
+
"aria-expanded": open,
|
|
5333
|
+
onClick: () => setOpen((v) => !v)
|
|
5334
|
+
}
|
|
5335
|
+
) }),
|
|
5336
|
+
/* @__PURE__ */ jsx58(
|
|
5337
|
+
Popover,
|
|
5338
|
+
{
|
|
5339
|
+
open,
|
|
5340
|
+
anchorRef,
|
|
5341
|
+
onClose: () => setOpen(false),
|
|
5342
|
+
placement: "bottom-end",
|
|
5343
|
+
role: "menu",
|
|
5344
|
+
label: "All flytedesk apps",
|
|
5345
|
+
width: 240,
|
|
5346
|
+
maxHeight: 360,
|
|
5347
|
+
children: /* @__PURE__ */ jsx58(Menu, { items, onClose: () => setOpen(false) })
|
|
5348
|
+
}
|
|
5349
|
+
)
|
|
5350
|
+
] });
|
|
5351
|
+
}
|
|
5352
|
+
|
|
5353
|
+
// src/components/platform/AppearanceMenu.tsx
|
|
5354
|
+
import * as React34 from "react";
|
|
5355
|
+
|
|
5356
|
+
// src/kits/appearance.ts
|
|
5357
|
+
import * as React33 from "react";
|
|
5358
|
+
var DEFAULT_STORAGE_KEY = "fd-appearance";
|
|
5359
|
+
var DEFAULT_PREFS = {
|
|
5360
|
+
theme: "system",
|
|
5361
|
+
textScale: "md",
|
|
5362
|
+
highContrast: false,
|
|
5363
|
+
reduceMotion: false
|
|
5364
|
+
};
|
|
5365
|
+
function resolveDefaults(opts) {
|
|
5366
|
+
return { ...DEFAULT_PREFS, ...opts?.defaults };
|
|
5367
|
+
}
|
|
5368
|
+
function resolveStorage(opts) {
|
|
5369
|
+
if (opts?.storage) return opts.storage;
|
|
5370
|
+
return typeof window !== "undefined" ? window.localStorage : void 0;
|
|
5371
|
+
}
|
|
5372
|
+
function resolveTheme(mode2) {
|
|
5373
|
+
if (mode2 !== "system") return mode2;
|
|
5374
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
5375
|
+
}
|
|
5376
|
+
function loadPrefs(opts) {
|
|
5377
|
+
const defaults = resolveDefaults(opts);
|
|
5378
|
+
const storage = resolveStorage(opts);
|
|
5379
|
+
const key = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
5380
|
+
if (!storage) return defaults;
|
|
5381
|
+
let stored;
|
|
5382
|
+
try {
|
|
5383
|
+
const raw = storage.getItem(key);
|
|
5384
|
+
stored = raw ? JSON.parse(raw) : null;
|
|
5385
|
+
} catch {
|
|
5386
|
+
stored = null;
|
|
5387
|
+
}
|
|
5388
|
+
if (typeof stored !== "object" || stored === null) return defaults;
|
|
5389
|
+
const s = stored;
|
|
5390
|
+
return {
|
|
5391
|
+
theme: s.theme === "light" || s.theme === "dark" || s.theme === "system" ? s.theme : defaults.theme,
|
|
5392
|
+
textScale: s.textScale === "sm" || s.textScale === "md" || s.textScale === "lg" || s.textScale === "xl" ? s.textScale : defaults.textScale,
|
|
5393
|
+
highContrast: typeof s.highContrast === "boolean" ? s.highContrast : defaults.highContrast,
|
|
5394
|
+
reduceMotion: typeof s.reduceMotion === "boolean" ? s.reduceMotion : defaults.reduceMotion
|
|
5395
|
+
};
|
|
5396
|
+
}
|
|
5397
|
+
function savePrefs(prefs, opts) {
|
|
5398
|
+
const storage = resolveStorage(opts);
|
|
5399
|
+
if (!storage) return;
|
|
5400
|
+
const key = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
5401
|
+
try {
|
|
5402
|
+
storage.setItem(key, JSON.stringify(prefs));
|
|
5403
|
+
} catch {
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
function createLocalStorageAppearanceStore(opts) {
|
|
5407
|
+
const key = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
5408
|
+
const storage = resolveStorage(opts);
|
|
5409
|
+
let snapshot = loadPrefs(opts);
|
|
5410
|
+
const listeners3 = /* @__PURE__ */ new Set();
|
|
5411
|
+
const notify = () => listeners3.forEach((l) => l());
|
|
5412
|
+
const onStorageEvent = (e) => {
|
|
5413
|
+
if (e.key !== null && e.key !== key) return;
|
|
5414
|
+
snapshot = loadPrefs(opts);
|
|
5415
|
+
notify();
|
|
5416
|
+
};
|
|
5417
|
+
return {
|
|
5418
|
+
getSnapshot: () => snapshot,
|
|
5419
|
+
write(prefs) {
|
|
5420
|
+
snapshot = prefs;
|
|
5421
|
+
if (storage) {
|
|
5422
|
+
try {
|
|
5423
|
+
storage.setItem(key, JSON.stringify(prefs));
|
|
5424
|
+
} catch {
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
notify();
|
|
5428
|
+
},
|
|
5429
|
+
subscribe(onChange) {
|
|
5430
|
+
listeners3.add(onChange);
|
|
5431
|
+
if (listeners3.size === 1 && typeof window !== "undefined") {
|
|
5432
|
+
window.addEventListener("storage", onStorageEvent);
|
|
5433
|
+
}
|
|
5434
|
+
return () => {
|
|
5435
|
+
listeners3.delete(onChange);
|
|
5436
|
+
if (listeners3.size === 0 && typeof window !== "undefined") {
|
|
5437
|
+
window.removeEventListener("storage", onStorageEvent);
|
|
5438
|
+
}
|
|
5439
|
+
};
|
|
5440
|
+
}
|
|
5441
|
+
};
|
|
5442
|
+
}
|
|
5443
|
+
function applyPrefs(prefs, root = document.documentElement) {
|
|
5444
|
+
root.setAttribute("data-theme", resolveTheme(prefs.theme));
|
|
5445
|
+
root.setAttribute("data-text-scale", prefs.textScale);
|
|
5446
|
+
if (prefs.highContrast) root.setAttribute("data-contrast", "high");
|
|
5447
|
+
else root.removeAttribute("data-contrast");
|
|
5448
|
+
if (prefs.reduceMotion) root.setAttribute("data-motion", "reduce");
|
|
5449
|
+
else root.removeAttribute("data-motion");
|
|
5450
|
+
}
|
|
5451
|
+
function useAppearancePrefs(store) {
|
|
5452
|
+
const prefs = React33.useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
5453
|
+
React33.useEffect(() => {
|
|
5454
|
+
applyPrefs(prefs);
|
|
5455
|
+
}, [prefs]);
|
|
5456
|
+
React33.useEffect(() => {
|
|
5457
|
+
if (prefs.theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
5460
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
5461
|
+
const onChange = () => applyPrefs(prefs);
|
|
5462
|
+
mq.addEventListener("change", onChange);
|
|
5463
|
+
return () => mq.removeEventListener("change", onChange);
|
|
5464
|
+
}, [prefs]);
|
|
5465
|
+
const update = React33.useCallback(
|
|
5466
|
+
(patch) => {
|
|
5467
|
+
store.write({ ...store.getSnapshot(), ...patch });
|
|
5468
|
+
},
|
|
5469
|
+
[store]
|
|
5470
|
+
);
|
|
5471
|
+
return [prefs, update];
|
|
5472
|
+
}
|
|
5473
|
+
|
|
5474
|
+
// src/components/platform/AppearanceMenu.tsx
|
|
5475
|
+
import { jsx as jsx59, jsxs as jsxs55 } from "react/jsx-runtime";
|
|
5476
|
+
var THEME_OPTIONS = [
|
|
5477
|
+
{ value: "light", label: "Light" },
|
|
5478
|
+
{ value: "dark", label: "Dark" },
|
|
5479
|
+
{ value: "system", label: "System" }
|
|
5480
|
+
];
|
|
5481
|
+
var TEXT_SCALE_OPTIONS = [
|
|
5482
|
+
{ value: "sm", label: "S" },
|
|
5483
|
+
{ value: "md", label: "M" },
|
|
5484
|
+
{ value: "lg", label: "L" },
|
|
5485
|
+
{ value: "xl", label: "XL" }
|
|
5486
|
+
];
|
|
5487
|
+
function AppearanceMenu({ className = "", store: storeProp, storageKey, storage, defaults }) {
|
|
5488
|
+
const store = React34.useMemo(
|
|
5489
|
+
() => storeProp ?? createLocalStorageAppearanceStore({ storageKey, storage, defaults }),
|
|
5490
|
+
[storeProp, storageKey, storage, defaults]
|
|
5491
|
+
);
|
|
5492
|
+
const [prefs, update] = useAppearancePrefs(store);
|
|
5493
|
+
const [open, setOpen] = React34.useState(false);
|
|
5494
|
+
const anchorRef = React34.useRef(null);
|
|
5495
|
+
const patch = (p) => update(p);
|
|
5496
|
+
return /* @__PURE__ */ jsxs55(React34.Fragment, { children: [
|
|
5497
|
+
/* @__PURE__ */ jsx59("span", { ref: anchorRef, className: ["fd-appearance-anchor", className].filter(Boolean).join(" "), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsx59(
|
|
5498
|
+
IconButton,
|
|
5499
|
+
{
|
|
5500
|
+
icon: "circle-half",
|
|
5501
|
+
label: "Appearance settings",
|
|
5502
|
+
onClick: () => setOpen((v) => !v)
|
|
5503
|
+
}
|
|
5504
|
+
) }),
|
|
5505
|
+
/* @__PURE__ */ jsx59(
|
|
5506
|
+
Popover,
|
|
5507
|
+
{
|
|
5508
|
+
open,
|
|
5509
|
+
anchorRef,
|
|
5510
|
+
onClose: () => setOpen(false),
|
|
5511
|
+
placement: "bottom-end",
|
|
5512
|
+
padded: true,
|
|
5513
|
+
width: 260,
|
|
5514
|
+
label: "Appearance settings",
|
|
5515
|
+
children: /* @__PURE__ */ jsxs55("div", { className: "fd-stack", style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
5516
|
+
/* @__PURE__ */ jsxs55("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
|
|
5517
|
+
/* @__PURE__ */ jsx59("span", { className: "fd-field-label", children: "Theme" }),
|
|
5518
|
+
/* @__PURE__ */ jsx59(
|
|
5519
|
+
SegmentedControl,
|
|
5520
|
+
{
|
|
5521
|
+
options: THEME_OPTIONS,
|
|
5522
|
+
value: prefs.theme,
|
|
5523
|
+
onChange: (v) => patch({ theme: v }),
|
|
5524
|
+
"aria-label": "Theme"
|
|
5525
|
+
}
|
|
5526
|
+
)
|
|
5527
|
+
] }),
|
|
5528
|
+
/* @__PURE__ */ jsxs55("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
|
|
5529
|
+
/* @__PURE__ */ jsx59("span", { className: "fd-field-label", children: "Text size" }),
|
|
5530
|
+
/* @__PURE__ */ jsx59(
|
|
5531
|
+
SegmentedControl,
|
|
5532
|
+
{
|
|
5533
|
+
options: TEXT_SCALE_OPTIONS,
|
|
5534
|
+
value: prefs.textScale,
|
|
5535
|
+
onChange: (v) => patch({ textScale: v }),
|
|
5536
|
+
"aria-label": "Text size"
|
|
5537
|
+
}
|
|
5538
|
+
)
|
|
5539
|
+
] }),
|
|
5540
|
+
/* @__PURE__ */ jsx59(
|
|
5541
|
+
Switch,
|
|
5542
|
+
{
|
|
5543
|
+
label: "High contrast",
|
|
5544
|
+
description: "Stronger borders and text color",
|
|
5545
|
+
checked: prefs.highContrast,
|
|
5546
|
+
onChange: (e) => patch({ highContrast: e.target.checked })
|
|
5547
|
+
}
|
|
5548
|
+
),
|
|
5549
|
+
/* @__PURE__ */ jsx59(
|
|
5550
|
+
Switch,
|
|
5551
|
+
{
|
|
5552
|
+
label: "Reduce motion",
|
|
5553
|
+
description: "Minimize animations and transitions",
|
|
5554
|
+
checked: prefs.reduceMotion,
|
|
5555
|
+
onChange: (e) => patch({ reduceMotion: e.target.checked })
|
|
5556
|
+
}
|
|
5557
|
+
)
|
|
5558
|
+
] })
|
|
5559
|
+
}
|
|
5560
|
+
)
|
|
5561
|
+
] });
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5564
|
+
// src/components/platform/ApiSpecBrowser.tsx
|
|
5565
|
+
import * as React35 from "react";
|
|
5566
|
+
import { jsx as jsx60, jsxs as jsxs56 } from "react/jsx-runtime";
|
|
5567
|
+
var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
|
|
5568
|
+
function Json({ obj }) {
|
|
5569
|
+
return /* @__PURE__ */ jsx60("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
|
|
5570
|
+
}
|
|
5571
|
+
function Endpoint({ s, onRequest }) {
|
|
5572
|
+
const [tried, setTried] = React35.useState(null);
|
|
5573
|
+
const run = async () => {
|
|
5574
|
+
setTried("busy");
|
|
5575
|
+
const t0 = (window.performance || Date).now();
|
|
5576
|
+
try {
|
|
5577
|
+
const data = await onRequest(s.id, s.request || (s.isList ? { page: 1, pageSize: 5 } : {}));
|
|
5578
|
+
setTried({ ms: Math.round((window.performance || Date).now() - t0), data });
|
|
5579
|
+
} catch (e) {
|
|
5580
|
+
setTried({ ms: Math.round((window.performance || Date).now() - t0), error: String(e && e.message || e) });
|
|
5581
|
+
}
|
|
5582
|
+
};
|
|
5583
|
+
return /* @__PURE__ */ jsx60(Card, { elevation: "flat", padded: false, children: /* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 0 }, children: [
|
|
5584
|
+
/* @__PURE__ */ jsxs56("div", { className: "fd-row", style: { gap: 10, padding: "14px 18px", flexWrap: "wrap" }, children: [
|
|
5585
|
+
/* @__PURE__ */ jsx60(Badge, { tone: METHOD_TONE[s.method] || "neutral", children: s.method }),
|
|
5586
|
+
/* @__PURE__ */ jsx60("code", { className: "fd-mono", style: { fontSize: 12.5, fontWeight: 600, color: "var(--text)", wordBreak: "break-all" }, children: s.path }),
|
|
5587
|
+
s.isList ? /* @__PURE__ */ jsx60(Badge, { tone: "neutral", icon: "rows", children: "Paged list" }) : null,
|
|
5588
|
+
/* @__PURE__ */ jsx60("span", { style: { flex: 1 } }),
|
|
5589
|
+
/* @__PURE__ */ jsxs56("span", { className: "fd-body-sm fd-muted fd-mono", children: [
|
|
5590
|
+
s.latency[0],
|
|
5591
|
+
"\u2013",
|
|
5592
|
+
s.latency[1],
|
|
5593
|
+
"ms"
|
|
5594
|
+
] }),
|
|
5595
|
+
/* @__PURE__ */ jsx60(Button, { size: "sm", variant: "secondary", icon: "play", loading: tried === "busy", onClick: run, children: "Try it" })
|
|
5596
|
+
] }),
|
|
5597
|
+
/* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 14, padding: "0 18px 16px" }, children: [
|
|
5598
|
+
/* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
5599
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-body-sm", style: { fontWeight: 700 }, children: s.title }),
|
|
5600
|
+
/* @__PURE__ */ jsx60("p", { className: "fd-body-sm fd-secondary", style: { margin: 0, textWrap: "pretty" }, children: s.purpose }),
|
|
5601
|
+
s.usedBy && s.usedBy.length ? /* @__PURE__ */ jsx60("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap" }, children: s.usedBy.map((u) => /* @__PURE__ */ jsx60(Tag, { children: u }, u)) }) : null
|
|
5602
|
+
] }),
|
|
5603
|
+
/* @__PURE__ */ jsxs56("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(280px,1fr))", gap: 12, alignItems: "start" }, children: [
|
|
5604
|
+
s.request ? /* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
5605
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-overline fd-muted", children: "Request body" }),
|
|
5606
|
+
/* @__PURE__ */ jsx60(Json, { obj: s.request })
|
|
5607
|
+
] }) : null,
|
|
5608
|
+
s.query ? /* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
5609
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-overline fd-muted", children: "Query" }),
|
|
5610
|
+
/* @__PURE__ */ jsx60(Json, { obj: s.query })
|
|
5611
|
+
] }) : null,
|
|
5612
|
+
/* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
5613
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-overline fd-muted", children: "Response 200" }),
|
|
5614
|
+
/* @__PURE__ */ jsx60(Json, { obj: s.response })
|
|
5615
|
+
] })
|
|
5616
|
+
] }),
|
|
5617
|
+
s.notes ? /* @__PURE__ */ jsx60(Flag, { tone: "info", statement: "Implementation note", cost: s.notes, actions: null }) : null,
|
|
5618
|
+
tried && tried !== "busy" ? /* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
5619
|
+
/* @__PURE__ */ jsxs56("span", { className: "fd-row", style: { gap: 8 }, children: [
|
|
5620
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-overline fd-muted", children: "Simulated response" }),
|
|
5621
|
+
/* @__PURE__ */ jsxs56(Badge, { tone: tried.error ? "danger" : "success", icon: "timer", children: [
|
|
5622
|
+
tried.ms,
|
|
5623
|
+
"ms"
|
|
5624
|
+
] })
|
|
5625
|
+
] }),
|
|
5626
|
+
/* @__PURE__ */ jsx60(Json, { obj: tried.error ? { error: tried.error } : tried.data })
|
|
5627
|
+
] }) : null
|
|
5628
|
+
] })
|
|
5629
|
+
] }) });
|
|
5630
|
+
}
|
|
5631
|
+
function ApiSpecBrowser({
|
|
5632
|
+
spec = [],
|
|
5633
|
+
onRequest,
|
|
5634
|
+
modules,
|
|
5635
|
+
groups,
|
|
5636
|
+
title = "API specification",
|
|
5637
|
+
kicker = "Architecture",
|
|
5638
|
+
lede,
|
|
5639
|
+
sourceNote,
|
|
5640
|
+
conventions,
|
|
5641
|
+
openQuestions,
|
|
5642
|
+
className = "",
|
|
5643
|
+
...rest
|
|
5644
|
+
}) {
|
|
5645
|
+
const [q, setQ] = React35.useState("");
|
|
5646
|
+
const [method, setMethod] = React35.useState(null);
|
|
5647
|
+
const [mod, setMod] = React35.useState(null);
|
|
5648
|
+
const [listOnly, setListOnly] = React35.useState(false);
|
|
5649
|
+
const activeModule = modules && modules.find((m) => m.id === mod);
|
|
5650
|
+
const hits = spec.filter((s) => (!method || s.method === method) && (!listOnly || s.isList) && (!activeModule || activeModule.endpoints.indexOf(s.id) >= 0) && (!q || (s.path + " " + s.title + " " + s.purpose + " " + (s.usedBy || []).join(" ")).toLowerCase().includes(q.toLowerCase())));
|
|
5651
|
+
const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
|
|
5652
|
+
const methods = [...new Set(spec.map((s) => s.method))];
|
|
5653
|
+
const listCount = spec.filter((s) => s.isList).length;
|
|
5654
|
+
return /* @__PURE__ */ jsxs56("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 1100 }, ...rest, children: [
|
|
5655
|
+
/* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 10 }, children: [
|
|
5656
|
+
kicker ? /* @__PURE__ */ jsx60("span", { className: "fd-overline fd-muted", children: kicker }) : null,
|
|
5657
|
+
/* @__PURE__ */ jsx60("h1", { className: "fd-h1", style: { margin: 0 }, children: title }),
|
|
5658
|
+
lede ? /* @__PURE__ */ jsx60("p", { className: "fd-body fd-secondary fd-prose", style: { margin: 0 }, children: lede }) : null
|
|
5659
|
+
] }),
|
|
5660
|
+
modules && modules.length ? /* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 8 }, children: [
|
|
5661
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-overline fd-muted", children: "Filter by screen \u2014 every endpoint that screen depends on" }),
|
|
5662
|
+
/* @__PURE__ */ jsxs56("div", { className: "fd-row", style: { gap: 8, flexWrap: "wrap" }, children: [
|
|
5663
|
+
/* @__PURE__ */ jsxs56(Tag, { icon: "stack", selected: !mod, onClick: () => setMod(null), children: [
|
|
5664
|
+
"All screens ",
|
|
5665
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-mono", children: spec.length })
|
|
5666
|
+
] }),
|
|
5667
|
+
modules.map((m) => /* @__PURE__ */ jsxs56(Tag, { icon: m.icon, selected: mod === m.id, onClick: () => setMod(mod === m.id ? null : m.id), children: [
|
|
5668
|
+
m.label,
|
|
5669
|
+
" ",
|
|
5670
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-mono", children: m.endpoints.length })
|
|
5671
|
+
] }, m.id))
|
|
5672
|
+
] }),
|
|
5673
|
+
activeModule ? /* @__PURE__ */ jsx60(
|
|
5674
|
+
Flag,
|
|
5675
|
+
{
|
|
5676
|
+
tone: "info",
|
|
5677
|
+
statement: activeModule.label + " calls " + activeModule.endpoints.length + " endpoints.",
|
|
5678
|
+
cost: "Integration checklist for this screen: " + activeModule.endpoints.join(", ") + ". Wire these and the screen is done.",
|
|
5679
|
+
actions: /* @__PURE__ */ jsx60(Button, { size: "sm", variant: "ghost", icon: "x", onClick: () => setMod(null), children: "Show all screens" })
|
|
5680
|
+
}
|
|
5681
|
+
) : null
|
|
5682
|
+
] }) : null,
|
|
5683
|
+
sourceNote ? /* @__PURE__ */ jsx60(Flag, { tone: "info", statement: "Design-first: this page is the spec.", cost: sourceNote, actions: null }) : null,
|
|
5684
|
+
/* @__PURE__ */ jsxs56("div", { className: "fd-row", style: { gap: 10, flexWrap: "wrap" }, children: [
|
|
5685
|
+
/* @__PURE__ */ jsx60(Input, { icon: "magnifying-glass", placeholder: "Find an endpoint, screen, or behavior", value: q, onChange: (e) => setQ(e.target.value), style: { width: 300 } }),
|
|
5686
|
+
methods.map((m) => /* @__PURE__ */ jsxs56(Tag, { selected: method === m, onClick: () => setMethod(method === m ? null : m), children: [
|
|
5687
|
+
m,
|
|
5688
|
+
" ",
|
|
5689
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-mono", children: spec.filter((s) => s.method === m).length })
|
|
5690
|
+
] }, m)),
|
|
5691
|
+
listCount ? /* @__PURE__ */ jsxs56(Tag, { icon: "rows", selected: listOnly, onClick: () => setListOnly(!listOnly), children: [
|
|
5692
|
+
"Paged lists ",
|
|
5693
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-mono", children: listCount })
|
|
5694
|
+
] }) : null,
|
|
5695
|
+
/* @__PURE__ */ jsx60("span", { style: { flex: 1 } }),
|
|
5696
|
+
/* @__PURE__ */ jsxs56("span", { className: "fd-body-sm fd-muted", children: [
|
|
5697
|
+
hits.length,
|
|
5698
|
+
" of ",
|
|
5699
|
+
spec.length,
|
|
5700
|
+
" endpoints",
|
|
5701
|
+
listCount ? " \xB7 " + listCount + " paged" : ""
|
|
5702
|
+
] })
|
|
5703
|
+
] }),
|
|
5704
|
+
hits.length === 0 ? /* @__PURE__ */ jsx60(Card, { elevation: "flat", children: /* @__PURE__ */ jsx60("p", { className: "fd-body-sm fd-muted", style: { margin: 0 }, children: "No endpoint matches those filters." }) }) : effGroups.map(([g, ids]) => {
|
|
5705
|
+
const items = hits.filter((s) => ids.indexOf(s.id) >= 0);
|
|
5706
|
+
if (!items.length) return null;
|
|
5707
|
+
return /* @__PURE__ */ jsxs56("div", { className: "fd-stack", style: { gap: 12 }, children: [
|
|
5708
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-label-lg", children: g }),
|
|
5709
|
+
items.map((s) => /* @__PURE__ */ jsx60(Endpoint, { s, onRequest }, s.id))
|
|
5710
|
+
] }, g);
|
|
5711
|
+
}),
|
|
5712
|
+
conventions && conventions.length ? /* @__PURE__ */ jsx60(Card, { title: "Cross-cutting conventions", elevation: "flat", children: /* @__PURE__ */ jsx60("div", { className: "fd-stack", style: { gap: 10 }, children: conventions.map(([k, v]) => /* @__PURE__ */ jsxs56("div", { className: "fd-row", style: { gap: 12, alignItems: "flex-start" }, children: [
|
|
5713
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-overline fd-muted", style: { width: 110, flex: "none", paddingTop: 2 }, children: k }),
|
|
5714
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-body-sm fd-secondary", style: { textWrap: "pretty" }, children: v })
|
|
5715
|
+
] }, k)) }) }) : null,
|
|
5716
|
+
openQuestions && openQuestions.length ? /* @__PURE__ */ jsx60(Collapsible, { icon: "list-checks", title: "Open questions before implementation", subtitle: openQuestions.length + " unresolved", children: /* @__PURE__ */ jsx60("div", { className: "fd-stack", style: { gap: 8 }, children: openQuestions.map(([id, question, why]) => /* @__PURE__ */ jsxs56("div", { className: "fd-row", style: { gap: 10, alignItems: "flex-start", padding: "8px 0", borderTop: "1px solid var(--border)" }, children: [
|
|
5717
|
+
/* @__PURE__ */ jsx60(Badge, { tone: "danger", children: id }),
|
|
5718
|
+
/* @__PURE__ */ jsxs56("span", { className: "fd-stack", style: { gap: 2, flex: 1 }, children: [
|
|
5719
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-body-sm", style: { fontWeight: 600 }, children: question }),
|
|
5720
|
+
/* @__PURE__ */ jsx60("span", { className: "fd-body-sm fd-muted", children: why })
|
|
5721
|
+
] })
|
|
5722
|
+
] }, id)) }) }) : null
|
|
5723
|
+
] });
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5726
|
+
// src/components/platform/ProfilePage.tsx
|
|
5727
|
+
import * as React36 from "react";
|
|
5728
|
+
import { jsx as jsx61, jsxs as jsxs57 } from "react/jsx-runtime";
|
|
5729
|
+
function fieldInitialValue(field, user) {
|
|
5730
|
+
if (field.value) return field.value(user);
|
|
5731
|
+
const raw = user[field.key];
|
|
5732
|
+
return raw == null ? "" : String(raw);
|
|
5733
|
+
}
|
|
5734
|
+
function groupFields(fields) {
|
|
5735
|
+
const order = [];
|
|
5736
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
5737
|
+
for (const field of fields) {
|
|
5738
|
+
const group = field.group ?? "Identity";
|
|
5739
|
+
if (!byGroup.has(group)) {
|
|
5740
|
+
byGroup.set(group, { group, description: field.groupDescription, fields: [] });
|
|
5741
|
+
order.push(group);
|
|
5742
|
+
}
|
|
5743
|
+
const entry = byGroup.get(group);
|
|
5744
|
+
if (entry.description === void 0) entry.description = field.groupDescription;
|
|
5745
|
+
entry.fields.push(field);
|
|
5746
|
+
}
|
|
5747
|
+
return order.map((g) => byGroup.get(g));
|
|
5748
|
+
}
|
|
5749
|
+
function ProfilePage({
|
|
5750
|
+
user: userProp,
|
|
5751
|
+
fields = [],
|
|
5752
|
+
notifications = [],
|
|
5753
|
+
extra,
|
|
5754
|
+
onChangePhoto,
|
|
5755
|
+
onSave,
|
|
5756
|
+
onNavigate,
|
|
5757
|
+
sessions,
|
|
5758
|
+
onSignOutEverywhere,
|
|
5759
|
+
className = ""
|
|
5760
|
+
}) {
|
|
5761
|
+
const { user: sessionUser, roles, permissions, saveProfile: saveProfile2 } = useSessionContext();
|
|
5762
|
+
const user = userProp || sessionUser;
|
|
5763
|
+
const initialFieldValues = React36.useMemo(() => {
|
|
5764
|
+
const values2 = {};
|
|
5765
|
+
for (const field of fields) values2[field.key] = fieldInitialValue(field, user);
|
|
5766
|
+
return values2;
|
|
5767
|
+
}, [user.id, fields]);
|
|
5768
|
+
const initialNotify = React36.useMemo(() => {
|
|
5769
|
+
const values2 = {};
|
|
5770
|
+
for (const n of notifications) values2[n.key] = !!user.notify?.[n.key];
|
|
5771
|
+
return values2;
|
|
5772
|
+
}, [user.id, notifications]);
|
|
5773
|
+
const [values, setValues] = React36.useState(initialFieldValues);
|
|
5774
|
+
const [notify, setNotify] = React36.useState(initialNotify);
|
|
5775
|
+
const [saving, setSaving] = React36.useState(false);
|
|
5776
|
+
const [saved, setSaved] = React36.useState(false);
|
|
5777
|
+
const [prevUserId, setPrevUserId] = React36.useState(user.id);
|
|
5778
|
+
if (user.id !== prevUserId) {
|
|
5779
|
+
setPrevUserId(user.id);
|
|
5780
|
+
setValues(initialFieldValues);
|
|
5781
|
+
setNotify(initialNotify);
|
|
5782
|
+
setSaved(false);
|
|
5783
|
+
}
|
|
5784
|
+
const setField = (key, v) => {
|
|
5785
|
+
setValues((d) => ({ ...d, [key]: v }));
|
|
5786
|
+
setSaved(false);
|
|
5787
|
+
};
|
|
5788
|
+
const toggleNotify = (key) => {
|
|
5789
|
+
setNotify((d) => ({ ...d, [key]: !d[key] }));
|
|
5790
|
+
setSaved(false);
|
|
5791
|
+
};
|
|
5792
|
+
const dirty = fields.some((f) => values[f.key] !== initialFieldValues[f.key]) || notifications.some((n) => notify[n.key] !== initialNotify[n.key]);
|
|
5793
|
+
const canSave = !!(onSave || saveProfile2) && (fields.length > 0 || notifications.length > 0);
|
|
5794
|
+
const save = async () => {
|
|
5795
|
+
if (!canSave) return;
|
|
5796
|
+
setSaving(true);
|
|
5797
|
+
const draft = { ...values };
|
|
5798
|
+
if (notifications.length > 0) draft.notify = notify;
|
|
5799
|
+
try {
|
|
5800
|
+
if (onSave) await onSave(draft);
|
|
5801
|
+
else await saveProfile2(draft);
|
|
5802
|
+
setSaved(true);
|
|
5803
|
+
} finally {
|
|
5804
|
+
setSaving(false);
|
|
5805
|
+
}
|
|
5806
|
+
};
|
|
5807
|
+
const discard = () => {
|
|
5808
|
+
setValues(initialFieldValues);
|
|
5809
|
+
setNotify(initialNotify);
|
|
5810
|
+
setSaved(false);
|
|
5811
|
+
};
|
|
5812
|
+
const displayName = fields.some((f) => f.key === "name") ? values.name || user.name : user.name;
|
|
5813
|
+
const fieldGroups = groupFields(fields);
|
|
5814
|
+
return /* @__PURE__ */ jsxs57("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, children: [
|
|
5815
|
+
/* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 10 }, children: [
|
|
5816
|
+
/* @__PURE__ */ jsx61("span", { className: "fd-overline fd-muted", children: "Account" }),
|
|
5817
|
+
/* @__PURE__ */ jsx61("h1", { className: "fd-h1", style: { margin: 0 }, children: "Your profile" }),
|
|
5818
|
+
/* @__PURE__ */ jsx61("p", { className: "fd-body fd-secondary fd-prose", style: { margin: 0 }, children: "How you appear to colleagues and clients across every flytedesk app, and what we send you." })
|
|
5819
|
+
] }),
|
|
5820
|
+
/* @__PURE__ */ jsx61(Card, { elevation: "flat", children: /* @__PURE__ */ jsxs57("div", { className: "fd-row", style: { gap: 16, flexWrap: "wrap", alignItems: "flex-start" }, children: [
|
|
5821
|
+
/* @__PURE__ */ jsx61(Avatar, { name: displayName, size: "lg" }),
|
|
5822
|
+
/* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 4, flex: 1, minWidth: 200 }, children: [
|
|
5823
|
+
/* @__PURE__ */ jsx61("span", { className: "fd-h3", style: { margin: 0 }, children: displayName }),
|
|
5824
|
+
/* @__PURE__ */ jsxs57("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap", paddingTop: 4 }, children: [
|
|
5825
|
+
roles.map((r) => /* @__PURE__ */ jsx61(Badge, { tone: r.id === "owner" ? "success" : "neutral", children: r.name }, r.id)),
|
|
5826
|
+
user.sso ? /* @__PURE__ */ jsx61(Badge, { tone: "info", icon: "shield-check", children: "SSO" }) : null
|
|
5827
|
+
] })
|
|
5828
|
+
] }),
|
|
5829
|
+
onChangePhoto ? /* @__PURE__ */ jsx61(Button, { variant: "secondary", size: "sm", icon: "image", onClick: onChangePhoto, children: "Change photo" }) : null
|
|
5830
|
+
] }) }),
|
|
5831
|
+
fieldGroups.map(({ group, description, fields: groupFieldDefs }) => /* @__PURE__ */ jsx61(
|
|
5832
|
+
Card,
|
|
5833
|
+
{
|
|
5834
|
+
title: description ? /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 2 }, children: [
|
|
5835
|
+
/* @__PURE__ */ jsx61("span", { children: group }),
|
|
5836
|
+
/* @__PURE__ */ jsx61("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: description })
|
|
5837
|
+
] }) : group,
|
|
5838
|
+
elevation: "flat",
|
|
5839
|
+
children: /* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 14 }, children: [
|
|
5840
|
+
/* @__PURE__ */ jsx61("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: groupFieldDefs.filter((f) => f.type !== "textarea").map((field) => {
|
|
5841
|
+
const help = typeof field.help === "function" ? field.help(user) : field.help;
|
|
5842
|
+
const readOnly = field.readOnly || field.type === "readonly";
|
|
5843
|
+
if (field.type === "select") {
|
|
5844
|
+
return /* @__PURE__ */ jsx61(
|
|
5845
|
+
Select,
|
|
5846
|
+
{
|
|
5847
|
+
label: field.label,
|
|
5848
|
+
value: values[field.key],
|
|
5849
|
+
onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
|
|
5850
|
+
options: field.options ?? [],
|
|
5851
|
+
placeholder: field.placeholder,
|
|
5852
|
+
help,
|
|
5853
|
+
disabled: readOnly
|
|
5854
|
+
},
|
|
5855
|
+
field.key
|
|
5856
|
+
);
|
|
5857
|
+
}
|
|
5858
|
+
return /* @__PURE__ */ jsx61(
|
|
5859
|
+
Input,
|
|
5860
|
+
{
|
|
5861
|
+
label: field.label,
|
|
5862
|
+
value: values[field.key],
|
|
5863
|
+
onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
|
|
5864
|
+
readOnly,
|
|
5865
|
+
disabled: readOnly,
|
|
5866
|
+
placeholder: field.placeholder,
|
|
5867
|
+
help
|
|
5868
|
+
},
|
|
5869
|
+
field.key
|
|
5870
|
+
);
|
|
5871
|
+
}) }),
|
|
5872
|
+
groupFieldDefs.filter((f) => f.type === "textarea").map((field) => {
|
|
5873
|
+
const help = typeof field.help === "function" ? field.help(user) : field.help;
|
|
5874
|
+
return /* @__PURE__ */ jsx61(
|
|
5875
|
+
Textarea,
|
|
5876
|
+
{
|
|
5877
|
+
label: field.label,
|
|
5878
|
+
value: values[field.key],
|
|
5879
|
+
onChange: field.readOnly ? void 0 : (e) => setField(field.key, e.target.value),
|
|
5880
|
+
readOnly: field.readOnly,
|
|
5881
|
+
disabled: field.readOnly,
|
|
5882
|
+
rows: field.rows ?? 3,
|
|
5883
|
+
placeholder: field.placeholder,
|
|
5884
|
+
help
|
|
5885
|
+
},
|
|
5886
|
+
field.key
|
|
5887
|
+
);
|
|
5888
|
+
})
|
|
5889
|
+
] })
|
|
5890
|
+
},
|
|
5891
|
+
group
|
|
5892
|
+
)),
|
|
5893
|
+
extra,
|
|
5894
|
+
notifications.length > 0 ? /* @__PURE__ */ jsx61(Card, { title: "Notifications", elevation: "flat", children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 14 }, children: notifications.map((n) => /* @__PURE__ */ jsx61(Switch, { checked: !!notify[n.key], onChange: () => toggleNotify(n.key), label: n.label, description: n.description }, n.key)) }) }) : null,
|
|
5895
|
+
/* @__PURE__ */ jsxs57(
|
|
5896
|
+
Card,
|
|
5897
|
+
{
|
|
5898
|
+
title: /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 2 }, children: [
|
|
5899
|
+
/* @__PURE__ */ jsx61("span", { children: "Access" }),
|
|
5900
|
+
/* @__PURE__ */ jsx61("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "What your roles grant you" })
|
|
5901
|
+
] }),
|
|
5902
|
+
elevation: "flat",
|
|
5903
|
+
action: /* @__PURE__ */ jsx61(
|
|
5904
|
+
Button,
|
|
5905
|
+
{
|
|
5906
|
+
size: "sm",
|
|
5907
|
+
variant: "ghost",
|
|
5908
|
+
icon: "arrow-right",
|
|
5909
|
+
onClick: () => onNavigate && onNavigate("#roles"),
|
|
5910
|
+
children: "See role definitions"
|
|
5911
|
+
}
|
|
5912
|
+
),
|
|
5913
|
+
children: [
|
|
5914
|
+
/* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 0 }, children: [
|
|
5915
|
+
/* @__PURE__ */ jsx61(CardRow, { label: "Roles", children: roles.map((r) => r.name).join(", ") || "None" }),
|
|
5916
|
+
/* @__PURE__ */ jsxs57(CardRow, { label: "Permissions", children: [
|
|
5917
|
+
permissions.length,
|
|
5918
|
+
" granted"
|
|
5919
|
+
] }),
|
|
5920
|
+
/* @__PURE__ */ jsx61(CardRow, { label: "Member since", children: user.joined || "\u2014" })
|
|
5921
|
+
] }),
|
|
5922
|
+
/* @__PURE__ */ jsx61("p", { className: "fd-body-sm fd-muted", style: { margin: "12px 0 0" }, children: "You cannot change your own roles \u2014 that is the point of them. An administrator manages roles from Admin \u2192 Users." })
|
|
5923
|
+
]
|
|
5924
|
+
}
|
|
5925
|
+
),
|
|
5926
|
+
sessions ? /* @__PURE__ */ jsx61(
|
|
5927
|
+
Card,
|
|
5928
|
+
{
|
|
5929
|
+
title: "Signed-in devices",
|
|
5930
|
+
elevation: "flat",
|
|
5931
|
+
action: /* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", icon: "sign-out", onClick: onSignOutEverywhere, children: "Sign out everywhere" }),
|
|
5932
|
+
children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 0 }, children: sessions.length === 0 ? /* @__PURE__ */ jsx61("p", { className: "fd-body-sm fd-muted", style: { margin: 0 }, children: "No other signed-in devices." }) : sessions.map((s) => /* @__PURE__ */ jsxs57("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
|
|
5933
|
+
/* @__PURE__ */ jsx61("i", { className: "ph ph-device-mobile", style: { fontSize: 16, color: "var(--text-muted)" }, "aria-hidden": "true" }),
|
|
5934
|
+
/* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 1, flex: 1, minWidth: 160 }, children: [
|
|
5935
|
+
/* @__PURE__ */ jsx61("span", { className: "fd-body-sm", style: { fontWeight: 600 }, children: s.device }),
|
|
5936
|
+
/* @__PURE__ */ jsxs57("span", { className: "fd-body-sm fd-muted", children: [
|
|
5937
|
+
s.where,
|
|
5938
|
+
" \xB7 ",
|
|
5939
|
+
s.when
|
|
5940
|
+
] })
|
|
5941
|
+
] }),
|
|
5942
|
+
s.current ? /* @__PURE__ */ jsx61(Badge, { tone: "success", dot: true, children: "This device" }) : /* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", children: "Revoke" })
|
|
5943
|
+
] }, s.id)) })
|
|
5944
|
+
}
|
|
5945
|
+
) : null,
|
|
5946
|
+
/* @__PURE__ */ jsxs57("div", { className: "fd-row", style: {
|
|
5947
|
+
gap: 10,
|
|
5948
|
+
flexWrap: "wrap",
|
|
5949
|
+
position: "sticky",
|
|
5950
|
+
bottom: 0,
|
|
5951
|
+
padding: "12px 14px",
|
|
5952
|
+
borderRadius: 10,
|
|
5953
|
+
background: "var(--surface)",
|
|
5954
|
+
border: "1px solid var(--border)",
|
|
5955
|
+
boxShadow: "0 -4px 16px rgba(11,13,17,.06)"
|
|
5956
|
+
}, children: [
|
|
5957
|
+
/* @__PURE__ */ jsx61("span", { className: "fd-body-sm", style: { fontWeight: 600, flex: 1 }, children: saved ? "Saved." : dirty ? "Unsaved changes" : "No pending changes" }),
|
|
5958
|
+
/* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", disabled: !dirty || saving, onClick: discard, children: "Discard" }),
|
|
5959
|
+
/* @__PURE__ */ jsx61(Button, { size: "sm", icon: "check", disabled: !dirty || !canSave, loading: saving, onClick: save, children: "Save changes" })
|
|
5960
|
+
] })
|
|
5961
|
+
] });
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5964
|
+
// src/components/platform/RoadmapTimeline.tsx
|
|
5965
|
+
import * as React39 from "react";
|
|
5966
|
+
|
|
5967
|
+
// src/kits/session.ts
|
|
5968
|
+
import * as React37 from "react";
|
|
5142
5969
|
var PERMISSION_CATALOG = [
|
|
5143
5970
|
{ group: "Plans", items: [
|
|
5144
5971
|
{ key: "plan.view", label: "View plans", detail: "Read any plan in the workspace." },
|
|
@@ -6620,627 +7447,146 @@ function saveProfile(patch) {
|
|
|
6620
7447
|
return s.user;
|
|
6621
7448
|
}
|
|
6622
7449
|
function refresh() {
|
|
6623
|
-
const id = getSession().user.id;
|
|
6624
|
-
current = null;
|
|
6625
|
-
const s = loadSession(id);
|
|
6626
|
-
listeners.forEach((fn) => fn(s));
|
|
6627
|
-
return s;
|
|
6628
|
-
}
|
|
6629
|
-
function subscribe(fn) {
|
|
6630
|
-
listeners.add(fn);
|
|
6631
|
-
return () => listeners.delete(fn);
|
|
6632
|
-
}
|
|
6633
|
-
function can(permission) {
|
|
6634
|
-
if (!permission) return true;
|
|
6635
|
-
const s = getSession();
|
|
6636
|
-
const list = Array.isArray(permission) ? permission : [permission];
|
|
6637
|
-
return list.every((p) => s.permissions.indexOf(p) >= 0);
|
|
6638
|
-
}
|
|
6639
|
-
function canAny(permissions) {
|
|
6640
|
-
const s = getSession();
|
|
6641
|
-
return (permissions || []).some((p) => s.permissions.indexOf(p) >= 0);
|
|
6642
|
-
}
|
|
6643
|
-
function hasRole(roleId) {
|
|
6644
|
-
return getSession().user.roles.indexOf(roleId) >= 0;
|
|
6645
|
-
}
|
|
6646
|
-
function flagOn(key) {
|
|
6647
|
-
return !!getSession().flags[key];
|
|
6648
|
-
}
|
|
6649
|
-
function findFlag(key) {
|
|
6650
|
-
return (getSession().allFlags || []).find((f) => f.key === key) || null;
|
|
6651
|
-
}
|
|
6652
|
-
function flagState(key) {
|
|
6653
|
-
if (!key) return "on";
|
|
6654
|
-
const f = findFlag(key);
|
|
6655
|
-
if (!f) return "on";
|
|
6656
|
-
return flagOn(key) ? "on" : "off";
|
|
6657
|
-
}
|
|
6658
|
-
function isEnabled(key) {
|
|
6659
|
-
return flagState(key) !== "off";
|
|
6660
|
-
}
|
|
6661
|
-
function flagVisible(key) {
|
|
6662
|
-
return isEnabled(key);
|
|
6663
|
-
}
|
|
6664
|
-
function flagLive(key) {
|
|
6665
|
-
return isEnabled(key);
|
|
6666
|
-
}
|
|
6667
|
-
function roadmap(overrides) {
|
|
6668
|
-
const flags = overrides && overrides.flags || (getSession().allFlags || []);
|
|
6669
|
-
const isComplete = overrides && overrides.isComplete || (() => false);
|
|
6670
|
-
const items = flags.map((f) => {
|
|
6671
|
-
const done = !!isComplete(f.key);
|
|
6672
|
-
const date = done ? f.shippedAt || f.eta : f.eta || f.shippedAt;
|
|
6673
|
-
const phaseInfo = (f.phase != null ? PHASES[f.phase] : void 0) || {};
|
|
6674
|
-
return {
|
|
6675
|
-
key: f.key,
|
|
6676
|
-
label: f.label,
|
|
6677
|
-
project: f.project,
|
|
6678
|
-
description: f.description,
|
|
6679
|
-
backend: f.backend,
|
|
6680
|
-
effort: f.effort,
|
|
6681
|
-
owner: f.owner,
|
|
6682
|
-
dependsOn: f.dependsOn || [],
|
|
6683
|
-
screen: f.screen || null,
|
|
6684
|
-
enabled: f.enabled,
|
|
6685
|
-
/* Which build phase this sits in, and why that phase comes where it does. */
|
|
6686
|
-
phase: f.phase,
|
|
6687
|
-
phaseName: phaseInfo.name || "Unscheduled",
|
|
6688
|
-
phaseWhy: phaseInfo.why || null,
|
|
6689
|
-
phaseStart: phaseInfo.start || null,
|
|
6690
|
-
/* Derived, not stored. */
|
|
6691
|
-
implemented: done,
|
|
6692
|
-
date: date || null,
|
|
6693
|
-
/* True when the only date we have is when the UI was designed, not when it ships. */
|
|
6694
|
-
dateIsDesign: !done && !f.eta && !!f.shippedAt,
|
|
6695
|
-
status: done ? "shipped" : f.enabled ? "next" : "planned"
|
|
6696
|
-
};
|
|
6697
|
-
}).filter((i) => i.date);
|
|
6698
|
-
items.sort((a, b) => (a.phase ?? 0) - (b.phase ?? 0) || String(a.date).localeCompare(String(b.date)) || a.label.localeCompare(b.label));
|
|
6699
|
-
const nextIndex = items.findIndex((i) => !i.implemented);
|
|
6700
|
-
return {
|
|
6701
|
-
items,
|
|
6702
|
-
nextIndex: nextIndex < 0 ? items.length : nextIndex,
|
|
6703
|
-
shipped: items.filter((i) => i.implemented).length,
|
|
6704
|
-
remaining: items.filter((i) => !i.implemented).length
|
|
6705
|
-
};
|
|
6706
|
-
}
|
|
6707
|
-
function useSession() {
|
|
6708
|
-
const [s, setS] = React30.useState(getSession);
|
|
6709
|
-
React30.useEffect(() => subscribe(setS), []);
|
|
7450
|
+
const id = getSession().user.id;
|
|
7451
|
+
current = null;
|
|
7452
|
+
const s = loadSession(id);
|
|
7453
|
+
listeners.forEach((fn) => fn(s));
|
|
6710
7454
|
return s;
|
|
6711
7455
|
}
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
ROLES,
|
|
6716
|
-
FLAGS,
|
|
6717
|
-
USERS,
|
|
6718
|
-
loadSession,
|
|
6719
|
-
getSession,
|
|
6720
|
-
setActiveUser,
|
|
6721
|
-
saveProfile,
|
|
6722
|
-
refresh,
|
|
6723
|
-
subscribe,
|
|
6724
|
-
can,
|
|
6725
|
-
canAny,
|
|
6726
|
-
hasRole,
|
|
6727
|
-
flagOn,
|
|
6728
|
-
useSession,
|
|
6729
|
-
findFlag,
|
|
6730
|
-
flagState,
|
|
6731
|
-
isEnabled,
|
|
6732
|
-
flagVisible,
|
|
6733
|
-
flagLive,
|
|
6734
|
-
roadmap,
|
|
6735
|
-
evaluateFlag,
|
|
6736
|
-
explainFlag,
|
|
6737
|
-
bucket,
|
|
6738
|
-
buildSession
|
|
6739
|
-
};
|
|
6740
|
-
|
|
6741
|
-
// src/components/platform/AccountMenu.tsx
|
|
6742
|
-
import { Fragment as Fragment14, jsx as jsx57, jsxs as jsxs53 } from "react/jsx-runtime";
|
|
6743
|
-
var DEFAULT_LINKS = [
|
|
6744
|
-
{ id: "profile", label: "Your profile", icon: "user-circle", href: "../admin/index.html#profile" },
|
|
6745
|
-
{ id: "preferences", label: "Preferences", icon: "sliders-horizontal", href: "../admin/index.html#preferences" }
|
|
6746
|
-
];
|
|
6747
|
-
var DEFAULT_ADMIN_LINKS = [
|
|
6748
|
-
{ id: "users", label: "Users", icon: "users-three", href: "../admin/index.html#users", perm: "users.view" },
|
|
6749
|
-
{ id: "roles", label: "Roles & permissions", icon: "shield-check", href: "../admin/index.html#roles", perm: "roles.manage" },
|
|
6750
|
-
{ id: "flags", label: "Feature flags", icon: "toggle-right", href: "../admin/index.html#flags", perm: "flags.manage" }
|
|
6751
|
-
];
|
|
6752
|
-
function Item({ item, onPick }) {
|
|
6753
|
-
return /* @__PURE__ */ jsxs53(
|
|
6754
|
-
"button",
|
|
6755
|
-
{
|
|
6756
|
-
type: "button",
|
|
6757
|
-
role: "menuitem",
|
|
6758
|
-
className: "fd-acct-item",
|
|
6759
|
-
onClick: () => onPick(item),
|
|
6760
|
-
children: [
|
|
6761
|
-
/* @__PURE__ */ jsx57("i", { className: "ph ph-" + item.icon, "aria-hidden": "true" }),
|
|
6762
|
-
/* @__PURE__ */ jsx57("span", { style: { flex: 1 }, children: item.label }),
|
|
6763
|
-
item.badge ? /* @__PURE__ */ jsx57(Badge, { tone: "neutral", children: item.badge }) : null
|
|
6764
|
-
]
|
|
6765
|
-
}
|
|
6766
|
-
);
|
|
7456
|
+
function subscribe(fn) {
|
|
7457
|
+
listeners.add(fn);
|
|
7458
|
+
return () => listeners.delete(fn);
|
|
6767
7459
|
}
|
|
6768
|
-
function
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
showRoles = true,
|
|
6774
|
-
allowUserSwitch = true,
|
|
6775
|
-
className = "",
|
|
6776
|
-
...rest
|
|
6777
|
-
}) {
|
|
6778
|
-
const session = SessionKit.useSession();
|
|
6779
|
-
const [open, setOpen] = React31.useState(false);
|
|
6780
|
-
const [switching, setSwitching] = React31.useState(false);
|
|
6781
|
-
const ref = React31.useRef(null);
|
|
6782
|
-
const user = session.user;
|
|
6783
|
-
const visibleAdmin = adminLinks.filter((l) => !l.perm || SessionKit.can(l.perm));
|
|
6784
|
-
const pick = (item) => {
|
|
6785
|
-
setOpen(false);
|
|
6786
|
-
if (item.onSelect) return item.onSelect();
|
|
6787
|
-
if (onNavigate) return onNavigate(item.href, item);
|
|
6788
|
-
if (item.href) window.location.href = item.href;
|
|
6789
|
-
};
|
|
6790
|
-
const signOut = () => {
|
|
6791
|
-
setOpen(false);
|
|
6792
|
-
if (onSignOut) return onSignOut();
|
|
6793
|
-
window.alert("Signed out. (Simulated \u2014 no auth provider is wired up.)");
|
|
6794
|
-
};
|
|
6795
|
-
return /* @__PURE__ */ jsxs53(Fragment14, { children: [
|
|
6796
|
-
/* @__PURE__ */ jsxs53(
|
|
6797
|
-
"button",
|
|
6798
|
-
{
|
|
6799
|
-
type: "button",
|
|
6800
|
-
ref,
|
|
6801
|
-
className: ["fd-acct-trigger", className].filter(Boolean).join(" "),
|
|
6802
|
-
"aria-haspopup": "menu",
|
|
6803
|
-
"aria-expanded": open,
|
|
6804
|
-
"aria-label": "Account \u2014 " + user.name,
|
|
6805
|
-
onClick: () => setOpen((o) => !o),
|
|
6806
|
-
...rest,
|
|
6807
|
-
children: [
|
|
6808
|
-
/* @__PURE__ */ jsx57(Avatar, { name: user.name, size: "sm" }),
|
|
6809
|
-
/* @__PURE__ */ jsx57("i", { className: "ph ph-caret-down", "aria-hidden": "true", style: { fontSize: 11, color: "var(--text-muted)" } })
|
|
6810
|
-
]
|
|
6811
|
-
}
|
|
6812
|
-
),
|
|
6813
|
-
/* @__PURE__ */ jsx57(Popover, { open, anchorRef: ref, onClose: () => setOpen(false), placement: "bottom-end", width: 272, children: /* @__PURE__ */ jsxs53("div", { role: "menu", className: "fd-stack", style: { gap: 0 }, children: [
|
|
6814
|
-
/* @__PURE__ */ jsxs53("div", { className: "fd-row", style: { gap: 10, padding: "12px 14px", borderBottom: "1px solid var(--border)" }, children: [
|
|
6815
|
-
/* @__PURE__ */ jsx57(Avatar, { name: user.name }),
|
|
6816
|
-
/* @__PURE__ */ jsxs53("span", { className: "fd-stack", style: { gap: 1, minWidth: 0, flex: 1 }, children: [
|
|
6817
|
-
/* @__PURE__ */ jsx57("span", { className: "fd-body-sm", style: { fontWeight: 700 }, children: user.name }),
|
|
6818
|
-
/* @__PURE__ */ jsx57("span", { className: "fd-body-sm fd-muted fd-table-trunc", title: truncationTitle(user.email), children: user.email })
|
|
6819
|
-
] })
|
|
6820
|
-
] }),
|
|
6821
|
-
showRoles && session.roles.length ? /* @__PURE__ */ jsx57("div", { className: "fd-row", style: { gap: 6, padding: "10px 14px", flexWrap: "wrap", borderBottom: "1px solid var(--border)" }, children: session.roles.map((r) => /* @__PURE__ */ jsx57(Badge, { tone: r.id === "owner" ? "success" : "neutral", children: r.name }, r.id)) }) : null,
|
|
6822
|
-
/* @__PURE__ */ jsx57("div", { className: "fd-acct-group", children: links.map((l) => /* @__PURE__ */ jsx57(Item, { item: l, onPick: pick }, l.id)) }),
|
|
6823
|
-
visibleAdmin.length ? /* @__PURE__ */ jsxs53("div", { className: "fd-acct-group", style: { borderTop: "1px solid var(--border)" }, children: [
|
|
6824
|
-
/* @__PURE__ */ jsx57("span", { className: "fd-overline fd-muted", style: { padding: "8px 14px 4px", display: "block" }, children: "Administration" }),
|
|
6825
|
-
visibleAdmin.map((l) => /* @__PURE__ */ jsx57(Item, { item: l, onPick: pick }, l.id))
|
|
6826
|
-
] }) : null,
|
|
6827
|
-
allowUserSwitch ? /* @__PURE__ */ jsxs53("div", { className: "fd-acct-group", style: { borderTop: "1px solid var(--border)" }, children: [
|
|
6828
|
-
/* @__PURE__ */ jsxs53("button", { type: "button", role: "menuitem", className: "fd-acct-item", onClick: () => setSwitching((s) => !s), children: [
|
|
6829
|
-
/* @__PURE__ */ jsx57("i", { className: "ph ph-user-switch", "aria-hidden": "true" }),
|
|
6830
|
-
/* @__PURE__ */ jsx57("span", { style: { flex: 1 }, children: "View as another member" }),
|
|
6831
|
-
/* @__PURE__ */ jsx57("i", { className: "ph ph-caret-" + (switching ? "up" : "down"), "aria-hidden": "true", style: { fontSize: 11 } })
|
|
6832
|
-
] }),
|
|
6833
|
-
switching ? /* @__PURE__ */ jsx57("div", { className: "fd-stack", style: { gap: 0 }, children: session.allUsers.filter((u) => u.status === "active").map((u) => /* @__PURE__ */ jsxs53(
|
|
6834
|
-
"button",
|
|
6835
|
-
{
|
|
6836
|
-
type: "button",
|
|
6837
|
-
className: "fd-acct-item",
|
|
6838
|
-
"data-current": u.id === user.id ? "true" : "false",
|
|
6839
|
-
onClick: () => {
|
|
6840
|
-
SessionKit.setActiveUser(u.id);
|
|
6841
|
-
setOpen(false);
|
|
6842
|
-
setSwitching(false);
|
|
6843
|
-
},
|
|
6844
|
-
children: [
|
|
6845
|
-
/* @__PURE__ */ jsx57(Avatar, { name: u.name, size: "sm" }),
|
|
6846
|
-
/* @__PURE__ */ jsxs53("span", { className: "fd-stack", style: { gap: 0, flex: 1, minWidth: 0, alignItems: "flex-start" }, children: [
|
|
6847
|
-
/* @__PURE__ */ jsx57("span", { style: { fontWeight: u.id === user.id ? 700 : 500 }, children: u.name }),
|
|
6848
|
-
/* @__PURE__ */ jsx57("span", { className: "fd-muted", style: { fontSize: 11.5 }, children: u.roles.join(", ") })
|
|
6849
|
-
] }),
|
|
6850
|
-
u.id === user.id ? /* @__PURE__ */ jsx57("i", { className: "ph ph-check", "aria-hidden": "true", style: { color: "var(--ok-text)" } }) : null
|
|
6851
|
-
]
|
|
6852
|
-
},
|
|
6853
|
-
u.id
|
|
6854
|
-
)) }) : null
|
|
6855
|
-
] }) : null,
|
|
6856
|
-
/* @__PURE__ */ jsx57("div", { className: "fd-acct-group", style: { borderTop: "1px solid var(--border)" }, children: /* @__PURE__ */ jsxs53("button", { type: "button", role: "menuitem", className: "fd-acct-item", "data-danger": "true", onClick: signOut, children: [
|
|
6857
|
-
/* @__PURE__ */ jsx57("i", { className: "ph ph-sign-out", "aria-hidden": "true" }),
|
|
6858
|
-
/* @__PURE__ */ jsx57("span", { style: { flex: 1 }, children: "Sign out" })
|
|
6859
|
-
] }) })
|
|
6860
|
-
] }) })
|
|
6861
|
-
] });
|
|
7460
|
+
function can(permission) {
|
|
7461
|
+
if (!permission) return true;
|
|
7462
|
+
const s = getSession();
|
|
7463
|
+
const list = Array.isArray(permission) ? permission : [permission];
|
|
7464
|
+
return list.every((p) => s.permissions.indexOf(p) >= 0);
|
|
6862
7465
|
}
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
import { jsx as jsx58, jsxs as jsxs54 } from "react/jsx-runtime";
|
|
6867
|
-
var METHOD_TONE = { GET: "success", POST: "info", PATCH: "warning", PUT: "warning", DELETE: "danger" };
|
|
6868
|
-
function Json({ obj }) {
|
|
6869
|
-
return /* @__PURE__ */ jsx58("pre", { className: "fd-json", children: JSON.stringify(obj, null, 2) });
|
|
7466
|
+
function canAny(permissions) {
|
|
7467
|
+
const s = getSession();
|
|
7468
|
+
return (permissions || []).some((p) => s.permissions.indexOf(p) >= 0);
|
|
6870
7469
|
}
|
|
6871
|
-
function
|
|
6872
|
-
|
|
6873
|
-
const run = async () => {
|
|
6874
|
-
setTried("busy");
|
|
6875
|
-
const t0 = (window.performance || Date).now();
|
|
6876
|
-
try {
|
|
6877
|
-
const data = await onRequest(s.id, s.request || (s.isList ? { page: 1, pageSize: 5 } : {}));
|
|
6878
|
-
setTried({ ms: Math.round((window.performance || Date).now() - t0), data });
|
|
6879
|
-
} catch (e) {
|
|
6880
|
-
setTried({ ms: Math.round((window.performance || Date).now() - t0), error: String(e && e.message || e) });
|
|
6881
|
-
}
|
|
6882
|
-
};
|
|
6883
|
-
return /* @__PURE__ */ jsx58(Card, { elevation: "flat", padded: false, children: /* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 0 }, children: [
|
|
6884
|
-
/* @__PURE__ */ jsxs54("div", { className: "fd-row", style: { gap: 10, padding: "14px 18px", flexWrap: "wrap" }, children: [
|
|
6885
|
-
/* @__PURE__ */ jsx58(Badge, { tone: METHOD_TONE[s.method] || "neutral", children: s.method }),
|
|
6886
|
-
/* @__PURE__ */ jsx58("code", { className: "fd-mono", style: { fontSize: 12.5, fontWeight: 600, color: "var(--text)", wordBreak: "break-all" }, children: s.path }),
|
|
6887
|
-
s.isList ? /* @__PURE__ */ jsx58(Badge, { tone: "neutral", icon: "rows", children: "Paged list" }) : null,
|
|
6888
|
-
/* @__PURE__ */ jsx58("span", { style: { flex: 1 } }),
|
|
6889
|
-
/* @__PURE__ */ jsxs54("span", { className: "fd-body-sm fd-muted fd-mono", children: [
|
|
6890
|
-
s.latency[0],
|
|
6891
|
-
"\u2013",
|
|
6892
|
-
s.latency[1],
|
|
6893
|
-
"ms"
|
|
6894
|
-
] }),
|
|
6895
|
-
/* @__PURE__ */ jsx58(Button, { size: "sm", variant: "secondary", icon: "play", loading: tried === "busy", onClick: run, children: "Try it" })
|
|
6896
|
-
] }),
|
|
6897
|
-
/* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 14, padding: "0 18px 16px" }, children: [
|
|
6898
|
-
/* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
6899
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-body-sm", style: { fontWeight: 700 }, children: s.title }),
|
|
6900
|
-
/* @__PURE__ */ jsx58("p", { className: "fd-body-sm fd-secondary", style: { margin: 0, textWrap: "pretty" }, children: s.purpose }),
|
|
6901
|
-
s.usedBy && s.usedBy.length ? /* @__PURE__ */ jsx58("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap" }, children: s.usedBy.map((u) => /* @__PURE__ */ jsx58(Tag, { children: u }, u)) }) : null
|
|
6902
|
-
] }),
|
|
6903
|
-
/* @__PURE__ */ jsxs54("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(280px,1fr))", gap: 12, alignItems: "start" }, children: [
|
|
6904
|
-
s.request ? /* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
6905
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-overline fd-muted", children: "Request body" }),
|
|
6906
|
-
/* @__PURE__ */ jsx58(Json, { obj: s.request })
|
|
6907
|
-
] }) : null,
|
|
6908
|
-
s.query ? /* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
6909
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-overline fd-muted", children: "Query" }),
|
|
6910
|
-
/* @__PURE__ */ jsx58(Json, { obj: s.query })
|
|
6911
|
-
] }) : null,
|
|
6912
|
-
/* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
6913
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-overline fd-muted", children: "Response 200" }),
|
|
6914
|
-
/* @__PURE__ */ jsx58(Json, { obj: s.response })
|
|
6915
|
-
] })
|
|
6916
|
-
] }),
|
|
6917
|
-
s.notes ? /* @__PURE__ */ jsx58(Flag, { tone: "info", statement: "Implementation note", cost: s.notes, actions: null }) : null,
|
|
6918
|
-
tried && tried !== "busy" ? /* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 6 }, children: [
|
|
6919
|
-
/* @__PURE__ */ jsxs54("span", { className: "fd-row", style: { gap: 8 }, children: [
|
|
6920
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-overline fd-muted", children: "Simulated response" }),
|
|
6921
|
-
/* @__PURE__ */ jsxs54(Badge, { tone: tried.error ? "danger" : "success", icon: "timer", children: [
|
|
6922
|
-
tried.ms,
|
|
6923
|
-
"ms"
|
|
6924
|
-
] })
|
|
6925
|
-
] }),
|
|
6926
|
-
/* @__PURE__ */ jsx58(Json, { obj: tried.error ? { error: tried.error } : tried.data })
|
|
6927
|
-
] }) : null
|
|
6928
|
-
] })
|
|
6929
|
-
] }) });
|
|
7470
|
+
function hasRole(roleId) {
|
|
7471
|
+
return getSession().user.roles.indexOf(roleId) >= 0;
|
|
6930
7472
|
}
|
|
6931
|
-
function
|
|
6932
|
-
|
|
6933
|
-
onRequest,
|
|
6934
|
-
modules,
|
|
6935
|
-
groups,
|
|
6936
|
-
title = "API specification",
|
|
6937
|
-
kicker = "Architecture",
|
|
6938
|
-
lede,
|
|
6939
|
-
sourceNote,
|
|
6940
|
-
conventions,
|
|
6941
|
-
openQuestions,
|
|
6942
|
-
className = "",
|
|
6943
|
-
...rest
|
|
6944
|
-
}) {
|
|
6945
|
-
const [q, setQ] = React32.useState("");
|
|
6946
|
-
const [method, setMethod] = React32.useState(null);
|
|
6947
|
-
const [mod, setMod] = React32.useState(null);
|
|
6948
|
-
const [listOnly, setListOnly] = React32.useState(false);
|
|
6949
|
-
const activeModule = modules && modules.find((m) => m.id === mod);
|
|
6950
|
-
const hits = spec.filter((s) => (!method || s.method === method) && (!listOnly || s.isList) && (!activeModule || activeModule.endpoints.indexOf(s.id) >= 0) && (!q || (s.path + " " + s.title + " " + s.purpose + " " + (s.usedBy || []).join(" ")).toLowerCase().includes(q.toLowerCase())));
|
|
6951
|
-
const effGroups = groups && groups.length ? groups : [["All endpoints", spec.map((s) => s.id)]];
|
|
6952
|
-
const methods = [...new Set(spec.map((s) => s.method))];
|
|
6953
|
-
const listCount = spec.filter((s) => s.isList).length;
|
|
6954
|
-
return /* @__PURE__ */ jsxs54("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 1100 }, ...rest, children: [
|
|
6955
|
-
/* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 10 }, children: [
|
|
6956
|
-
kicker ? /* @__PURE__ */ jsx58("span", { className: "fd-overline fd-muted", children: kicker }) : null,
|
|
6957
|
-
/* @__PURE__ */ jsx58("h1", { className: "fd-h1", style: { margin: 0 }, children: title }),
|
|
6958
|
-
lede ? /* @__PURE__ */ jsx58("p", { className: "fd-body fd-secondary fd-prose", style: { margin: 0 }, children: lede }) : null
|
|
6959
|
-
] }),
|
|
6960
|
-
modules && modules.length ? /* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 8 }, children: [
|
|
6961
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-overline fd-muted", children: "Filter by screen \u2014 every endpoint that screen depends on" }),
|
|
6962
|
-
/* @__PURE__ */ jsxs54("div", { className: "fd-row", style: { gap: 8, flexWrap: "wrap" }, children: [
|
|
6963
|
-
/* @__PURE__ */ jsxs54(Tag, { icon: "stack", selected: !mod, onClick: () => setMod(null), children: [
|
|
6964
|
-
"All screens ",
|
|
6965
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-mono", children: spec.length })
|
|
6966
|
-
] }),
|
|
6967
|
-
modules.map((m) => /* @__PURE__ */ jsxs54(Tag, { icon: m.icon, selected: mod === m.id, onClick: () => setMod(mod === m.id ? null : m.id), children: [
|
|
6968
|
-
m.label,
|
|
6969
|
-
" ",
|
|
6970
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-mono", children: m.endpoints.length })
|
|
6971
|
-
] }, m.id))
|
|
6972
|
-
] }),
|
|
6973
|
-
activeModule ? /* @__PURE__ */ jsx58(
|
|
6974
|
-
Flag,
|
|
6975
|
-
{
|
|
6976
|
-
tone: "info",
|
|
6977
|
-
statement: activeModule.label + " calls " + activeModule.endpoints.length + " endpoints.",
|
|
6978
|
-
cost: "Integration checklist for this screen: " + activeModule.endpoints.join(", ") + ". Wire these and the screen is done.",
|
|
6979
|
-
actions: /* @__PURE__ */ jsx58(Button, { size: "sm", variant: "ghost", icon: "x", onClick: () => setMod(null), children: "Show all screens" })
|
|
6980
|
-
}
|
|
6981
|
-
) : null
|
|
6982
|
-
] }) : null,
|
|
6983
|
-
sourceNote ? /* @__PURE__ */ jsx58(Flag, { tone: "info", statement: "Design-first: this page is the spec.", cost: sourceNote, actions: null }) : null,
|
|
6984
|
-
/* @__PURE__ */ jsxs54("div", { className: "fd-row", style: { gap: 10, flexWrap: "wrap" }, children: [
|
|
6985
|
-
/* @__PURE__ */ jsx58(Input, { icon: "magnifying-glass", placeholder: "Find an endpoint, screen, or behavior", value: q, onChange: (e) => setQ(e.target.value), style: { width: 300 } }),
|
|
6986
|
-
methods.map((m) => /* @__PURE__ */ jsxs54(Tag, { selected: method === m, onClick: () => setMethod(method === m ? null : m), children: [
|
|
6987
|
-
m,
|
|
6988
|
-
" ",
|
|
6989
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-mono", children: spec.filter((s) => s.method === m).length })
|
|
6990
|
-
] }, m)),
|
|
6991
|
-
listCount ? /* @__PURE__ */ jsxs54(Tag, { icon: "rows", selected: listOnly, onClick: () => setListOnly(!listOnly), children: [
|
|
6992
|
-
"Paged lists ",
|
|
6993
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-mono", children: listCount })
|
|
6994
|
-
] }) : null,
|
|
6995
|
-
/* @__PURE__ */ jsx58("span", { style: { flex: 1 } }),
|
|
6996
|
-
/* @__PURE__ */ jsxs54("span", { className: "fd-body-sm fd-muted", children: [
|
|
6997
|
-
hits.length,
|
|
6998
|
-
" of ",
|
|
6999
|
-
spec.length,
|
|
7000
|
-
" endpoints",
|
|
7001
|
-
listCount ? " \xB7 " + listCount + " paged" : ""
|
|
7002
|
-
] })
|
|
7003
|
-
] }),
|
|
7004
|
-
hits.length === 0 ? /* @__PURE__ */ jsx58(Card, { elevation: "flat", children: /* @__PURE__ */ jsx58("p", { className: "fd-body-sm fd-muted", style: { margin: 0 }, children: "No endpoint matches those filters." }) }) : effGroups.map(([g, ids]) => {
|
|
7005
|
-
const items = hits.filter((s) => ids.indexOf(s.id) >= 0);
|
|
7006
|
-
if (!items.length) return null;
|
|
7007
|
-
return /* @__PURE__ */ jsxs54("div", { className: "fd-stack", style: { gap: 12 }, children: [
|
|
7008
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-label-lg", children: g }),
|
|
7009
|
-
items.map((s) => /* @__PURE__ */ jsx58(Endpoint, { s, onRequest }, s.id))
|
|
7010
|
-
] }, g);
|
|
7011
|
-
}),
|
|
7012
|
-
conventions && conventions.length ? /* @__PURE__ */ jsx58(Card, { title: "Cross-cutting conventions", elevation: "flat", children: /* @__PURE__ */ jsx58("div", { className: "fd-stack", style: { gap: 10 }, children: conventions.map(([k, v]) => /* @__PURE__ */ jsxs54("div", { className: "fd-row", style: { gap: 12, alignItems: "flex-start" }, children: [
|
|
7013
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-overline fd-muted", style: { width: 110, flex: "none", paddingTop: 2 }, children: k }),
|
|
7014
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-body-sm fd-secondary", style: { textWrap: "pretty" }, children: v })
|
|
7015
|
-
] }, k)) }) }) : null,
|
|
7016
|
-
openQuestions && openQuestions.length ? /* @__PURE__ */ jsx58(Collapsible, { icon: "list-checks", title: "Open questions before implementation", subtitle: openQuestions.length + " unresolved", children: /* @__PURE__ */ jsx58("div", { className: "fd-stack", style: { gap: 8 }, children: openQuestions.map(([id, question, why]) => /* @__PURE__ */ jsxs54("div", { className: "fd-row", style: { gap: 10, alignItems: "flex-start", padding: "8px 0", borderTop: "1px solid var(--border)" }, children: [
|
|
7017
|
-
/* @__PURE__ */ jsx58(Badge, { tone: "danger", children: id }),
|
|
7018
|
-
/* @__PURE__ */ jsxs54("span", { className: "fd-stack", style: { gap: 2, flex: 1 }, children: [
|
|
7019
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-body-sm", style: { fontWeight: 600 }, children: question }),
|
|
7020
|
-
/* @__PURE__ */ jsx58("span", { className: "fd-body-sm fd-muted", children: why })
|
|
7021
|
-
] })
|
|
7022
|
-
] }, id)) }) }) : null
|
|
7023
|
-
] });
|
|
7473
|
+
function flagOn(key) {
|
|
7474
|
+
return !!getSession().flags[key];
|
|
7024
7475
|
}
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
function
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7476
|
+
function findFlag(key) {
|
|
7477
|
+
return (getSession().allFlags || []).find((f) => f.key === key) || null;
|
|
7478
|
+
}
|
|
7479
|
+
function flagState(key) {
|
|
7480
|
+
if (!key) return "on";
|
|
7481
|
+
const f = findFlag(key);
|
|
7482
|
+
if (!f) return "on";
|
|
7483
|
+
return flagOn(key) ? "on" : "off";
|
|
7484
|
+
}
|
|
7485
|
+
function isEnabled(key) {
|
|
7486
|
+
return flagState(key) !== "off";
|
|
7487
|
+
}
|
|
7488
|
+
function flagVisible(key) {
|
|
7489
|
+
return isEnabled(key);
|
|
7490
|
+
}
|
|
7491
|
+
function flagLive(key) {
|
|
7492
|
+
return isEnabled(key);
|
|
7493
|
+
}
|
|
7494
|
+
function roadmap(overrides) {
|
|
7495
|
+
const flags = overrides && overrides.flags || (getSession().allFlags || []);
|
|
7496
|
+
const isComplete = overrides && overrides.isComplete || (() => false);
|
|
7497
|
+
const items = flags.map((f) => {
|
|
7498
|
+
const done = !!isComplete(f.key);
|
|
7499
|
+
const date = done ? f.shippedAt || f.eta : f.eta || f.shippedAt;
|
|
7500
|
+
const phaseInfo = (f.phase != null ? PHASES[f.phase] : void 0) || {};
|
|
7501
|
+
return {
|
|
7502
|
+
key: f.key,
|
|
7503
|
+
label: f.label,
|
|
7504
|
+
project: f.project,
|
|
7505
|
+
description: f.description,
|
|
7506
|
+
backend: f.backend,
|
|
7507
|
+
effort: f.effort,
|
|
7508
|
+
owner: f.owner,
|
|
7509
|
+
dependsOn: f.dependsOn || [],
|
|
7510
|
+
screen: f.screen || null,
|
|
7511
|
+
enabled: f.enabled,
|
|
7512
|
+
/* Which build phase this sits in, and why that phase comes where it does. */
|
|
7513
|
+
phase: f.phase,
|
|
7514
|
+
phaseName: phaseInfo.name || "Unscheduled",
|
|
7515
|
+
phaseWhy: phaseInfo.why || null,
|
|
7516
|
+
phaseStart: phaseInfo.start || null,
|
|
7517
|
+
/* Derived, not stored. */
|
|
7518
|
+
implemented: done,
|
|
7519
|
+
date: date || null,
|
|
7520
|
+
/* True when the only date we have is when the UI was designed, not when it ships. */
|
|
7521
|
+
dateIsDesign: !done && !f.eta && !!f.shippedAt,
|
|
7522
|
+
status: done ? "shipped" : f.enabled ? "next" : "planned"
|
|
7523
|
+
};
|
|
7524
|
+
}).filter((i) => i.date);
|
|
7525
|
+
items.sort((a, b) => (a.phase ?? 0) - (b.phase ?? 0) || String(a.date).localeCompare(String(b.date)) || a.label.localeCompare(b.label));
|
|
7526
|
+
const nextIndex = items.findIndex((i) => !i.implemented);
|
|
7527
|
+
return {
|
|
7528
|
+
items,
|
|
7529
|
+
nextIndex: nextIndex < 0 ? items.length : nextIndex,
|
|
7530
|
+
shipped: items.filter((i) => i.implemented).length,
|
|
7531
|
+
remaining: items.filter((i) => !i.implemented).length
|
|
7066
7532
|
};
|
|
7067
|
-
const liveSessions = sessions || [
|
|
7068
|
-
{ id: "s1", device: "MacBook Pro \xB7 Chrome", where: "Denver, CO", when: "Active now", current: true },
|
|
7069
|
-
{ id: "s2", device: "iPhone 15 \xB7 Safari", where: "Denver, CO", when: "2 hours ago" },
|
|
7070
|
-
{ id: "s3", device: "Windows \xB7 Edge", where: "Chicago, IL", when: "Aug 12" }
|
|
7071
|
-
];
|
|
7072
|
-
return /* @__PURE__ */ jsxs55("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, ...rest, children: [
|
|
7073
|
-
/* @__PURE__ */ jsxs55("div", { className: "fd-stack", style: { gap: 10 }, children: [
|
|
7074
|
-
/* @__PURE__ */ jsx59("span", { className: "fd-overline fd-muted", children: "Account" }),
|
|
7075
|
-
/* @__PURE__ */ jsx59("h1", { className: "fd-h1", style: { margin: 0 }, children: "Your profile" }),
|
|
7076
|
-
/* @__PURE__ */ jsx59("p", { className: "fd-body fd-secondary fd-prose", style: { margin: 0 }, children: "How you appear to colleagues and clients across every flytedesk app, and what we send you." })
|
|
7077
|
-
] }),
|
|
7078
|
-
/* @__PURE__ */ jsx59(Card, { elevation: "flat", children: /* @__PURE__ */ jsxs55("div", { className: "fd-row", style: { gap: 16, flexWrap: "wrap", alignItems: "flex-start" }, children: [
|
|
7079
|
-
/* @__PURE__ */ jsx59(Avatar, { name: draft.name || user.name, size: "lg" }),
|
|
7080
|
-
/* @__PURE__ */ jsxs55("div", { className: "fd-stack", style: { gap: 4, flex: 1, minWidth: 200 }, children: [
|
|
7081
|
-
/* @__PURE__ */ jsx59("span", { className: "fd-h3", style: { margin: 0 }, children: draft.name || user.name }),
|
|
7082
|
-
/* @__PURE__ */ jsxs55("span", { className: "fd-body-sm fd-muted", children: [
|
|
7083
|
-
draft.title || "No title set",
|
|
7084
|
-
" \xB7 ",
|
|
7085
|
-
user.team || "No team"
|
|
7086
|
-
] }),
|
|
7087
|
-
/* @__PURE__ */ jsxs55("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap", paddingTop: 4 }, children: [
|
|
7088
|
-
session.roles.map((r) => /* @__PURE__ */ jsx59(Badge, { tone: r.id === "owner" ? "success" : "neutral", children: r.name }, r.id)),
|
|
7089
|
-
user.sso ? /* @__PURE__ */ jsx59(Badge, { tone: "info", icon: "shield-check", children: "SSO" }) : null
|
|
7090
|
-
] })
|
|
7091
|
-
] }),
|
|
7092
|
-
/* @__PURE__ */ jsx59(Button, { variant: "secondary", size: "sm", icon: "image", children: "Change photo" })
|
|
7093
|
-
] }) }),
|
|
7094
|
-
/* @__PURE__ */ jsx59(
|
|
7095
|
-
Card,
|
|
7096
|
-
{
|
|
7097
|
-
title: /* @__PURE__ */ jsxs55("span", { className: "fd-stack", style: { gap: 2 }, children: [
|
|
7098
|
-
/* @__PURE__ */ jsx59("span", { children: "Identity" }),
|
|
7099
|
-
/* @__PURE__ */ jsx59("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Name and title appear on plans you share" })
|
|
7100
|
-
] }),
|
|
7101
|
-
elevation: "flat",
|
|
7102
|
-
children: /* @__PURE__ */ jsxs55("div", { className: "fd-stack", style: { gap: 14 }, children: [
|
|
7103
|
-
/* @__PURE__ */ jsxs55("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
|
|
7104
|
-
/* @__PURE__ */ jsx59(Input, { label: "Full name", value: draft.name, onChange: (e) => set("name", e.target.value) }),
|
|
7105
|
-
/* @__PURE__ */ jsx59(Input, { label: "Job title", value: draft.title, onChange: (e) => set("title", e.target.value), placeholder: "e.g. Senior media planner" })
|
|
7106
|
-
] }),
|
|
7107
|
-
/* @__PURE__ */ jsxs55("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
|
|
7108
|
-
/* @__PURE__ */ jsx59(
|
|
7109
|
-
Input,
|
|
7110
|
-
{
|
|
7111
|
-
label: "Work email",
|
|
7112
|
-
value: draft.email,
|
|
7113
|
-
readOnly: true,
|
|
7114
|
-
disabled: true,
|
|
7115
|
-
help: user.sso ? "Managed by your identity provider \u2014 change it there." : "Contact an administrator to change this."
|
|
7116
|
-
}
|
|
7117
|
-
),
|
|
7118
|
-
/* @__PURE__ */ jsx59(Input, { label: "Phone", value: draft.phone, onChange: (e) => set("phone", e.target.value), placeholder: "Optional" })
|
|
7119
|
-
] }),
|
|
7120
|
-
/* @__PURE__ */ jsx59(
|
|
7121
|
-
Textarea,
|
|
7122
|
-
{
|
|
7123
|
-
label: "Short bio",
|
|
7124
|
-
value: draft.bio,
|
|
7125
|
-
onChange: (e) => set("bio", e.target.value),
|
|
7126
|
-
rows: 3,
|
|
7127
|
-
placeholder: "Optional \u2014 shown to colleagues on shared plans.",
|
|
7128
|
-
help: "Never shown to clients."
|
|
7129
|
-
}
|
|
7130
|
-
)
|
|
7131
|
-
] })
|
|
7132
|
-
}
|
|
7133
|
-
),
|
|
7134
|
-
/* @__PURE__ */ jsx59(Card, { title: "Working preferences", elevation: "flat", children: /* @__PURE__ */ jsxs55("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
|
|
7135
|
-
/* @__PURE__ */ jsx59(
|
|
7136
|
-
Select,
|
|
7137
|
-
{
|
|
7138
|
-
label: "Time zone",
|
|
7139
|
-
value: draft.timezone,
|
|
7140
|
-
onChange: (e) => set("timezone", e.target.value),
|
|
7141
|
-
options: TIMEZONES,
|
|
7142
|
-
help: "Flight dates and schedules render in this zone."
|
|
7143
|
-
}
|
|
7144
|
-
),
|
|
7145
|
-
/* @__PURE__ */ jsx59(Select, { label: "Start of week", options: ["Monday", "Sunday"], placeholder: "Monday" })
|
|
7146
|
-
] }) }),
|
|
7147
|
-
/* @__PURE__ */ jsx59(
|
|
7148
|
-
Card,
|
|
7149
|
-
{
|
|
7150
|
-
title: /* @__PURE__ */ jsxs55("span", { className: "fd-stack", style: { gap: 2 }, children: [
|
|
7151
|
-
/* @__PURE__ */ jsx59("span", { children: "Notifications" }),
|
|
7152
|
-
/* @__PURE__ */ jsx59("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Email only for now \u2014 in-app notifications are on the roadmap" })
|
|
7153
|
-
] }),
|
|
7154
|
-
elevation: "flat",
|
|
7155
|
-
children: /* @__PURE__ */ jsx59("div", { className: "fd-stack", style: { gap: 14 }, children: NOTIFY.map((n) => /* @__PURE__ */ jsx59(Switch, { checked: !!draft.notify[n.key], onChange: () => setNotify(n.key), label: n.label, description: n.detail }, n.key)) })
|
|
7156
|
-
}
|
|
7157
|
-
),
|
|
7158
|
-
/* @__PURE__ */ jsxs55(
|
|
7159
|
-
Card,
|
|
7160
|
-
{
|
|
7161
|
-
title: /* @__PURE__ */ jsxs55("span", { className: "fd-stack", style: { gap: 2 }, children: [
|
|
7162
|
-
/* @__PURE__ */ jsx59("span", { children: "Access" }),
|
|
7163
|
-
/* @__PURE__ */ jsx59("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "What your roles grant you" })
|
|
7164
|
-
] }),
|
|
7165
|
-
elevation: "flat",
|
|
7166
|
-
action: /* @__PURE__ */ jsx59(
|
|
7167
|
-
Button,
|
|
7168
|
-
{
|
|
7169
|
-
size: "sm",
|
|
7170
|
-
variant: "ghost",
|
|
7171
|
-
icon: "arrow-right",
|
|
7172
|
-
onClick: () => onNavigate && onNavigate("#roles"),
|
|
7173
|
-
children: "See role definitions"
|
|
7174
|
-
}
|
|
7175
|
-
),
|
|
7176
|
-
children: [
|
|
7177
|
-
/* @__PURE__ */ jsxs55("div", { className: "fd-stack", style: { gap: 0 }, children: [
|
|
7178
|
-
/* @__PURE__ */ jsx59(CardRow, { label: "Roles", children: session.roles.map((r) => r.name).join(", ") || "None" }),
|
|
7179
|
-
/* @__PURE__ */ jsxs55(CardRow, { label: "Permissions", children: [
|
|
7180
|
-
session.permissions.length,
|
|
7181
|
-
" granted"
|
|
7182
|
-
] }),
|
|
7183
|
-
/* @__PURE__ */ jsx59(CardRow, { label: "Member since", children: user.joined || "\u2014" })
|
|
7184
|
-
] }),
|
|
7185
|
-
/* @__PURE__ */ jsx59("p", { className: "fd-body-sm fd-muted", style: { margin: "12px 0 0" }, children: "You cannot change your own roles \u2014 that is the point of them. An administrator manages roles from Admin \u2192 Users." })
|
|
7186
|
-
]
|
|
7187
|
-
}
|
|
7188
|
-
),
|
|
7189
|
-
showSessions ? /* @__PURE__ */ jsx59(
|
|
7190
|
-
Card,
|
|
7191
|
-
{
|
|
7192
|
-
title: "Signed-in devices",
|
|
7193
|
-
elevation: "flat",
|
|
7194
|
-
action: /* @__PURE__ */ jsx59(Button, { size: "sm", variant: "ghost", icon: "sign-out", children: "Sign out everywhere" }),
|
|
7195
|
-
children: /* @__PURE__ */ jsx59("div", { className: "fd-stack", style: { gap: 0 }, children: liveSessions.map((s) => /* @__PURE__ */ jsxs55("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
|
|
7196
|
-
/* @__PURE__ */ jsx59("i", { className: "ph ph-device-mobile", style: { fontSize: 16, color: "var(--text-muted)" }, "aria-hidden": "true" }),
|
|
7197
|
-
/* @__PURE__ */ jsxs55("span", { className: "fd-stack", style: { gap: 1, flex: 1, minWidth: 160 }, children: [
|
|
7198
|
-
/* @__PURE__ */ jsx59("span", { className: "fd-body-sm", style: { fontWeight: 600 }, children: s.device }),
|
|
7199
|
-
/* @__PURE__ */ jsxs55("span", { className: "fd-body-sm fd-muted", children: [
|
|
7200
|
-
s.where,
|
|
7201
|
-
" \xB7 ",
|
|
7202
|
-
s.when
|
|
7203
|
-
] })
|
|
7204
|
-
] }),
|
|
7205
|
-
s.current ? /* @__PURE__ */ jsx59(Badge, { tone: "success", dot: true, children: "This device" }) : /* @__PURE__ */ jsx59(Button, { size: "sm", variant: "ghost", children: "Revoke" })
|
|
7206
|
-
] }, s.id)) })
|
|
7207
|
-
}
|
|
7208
|
-
) : null,
|
|
7209
|
-
/* @__PURE__ */ jsxs55("div", { className: "fd-row", style: {
|
|
7210
|
-
gap: 10,
|
|
7211
|
-
flexWrap: "wrap",
|
|
7212
|
-
position: "sticky",
|
|
7213
|
-
bottom: 0,
|
|
7214
|
-
padding: "12px 14px",
|
|
7215
|
-
borderRadius: 10,
|
|
7216
|
-
background: "var(--surface)",
|
|
7217
|
-
border: "1px solid var(--border)",
|
|
7218
|
-
boxShadow: "0 -4px 16px rgba(11,13,17,.06)"
|
|
7219
|
-
}, children: [
|
|
7220
|
-
/* @__PURE__ */ jsx59("span", { className: "fd-body-sm", style: { fontWeight: 600, flex: 1 }, children: saved ? "Saved." : dirty ? "Unsaved changes" : "No pending changes" }),
|
|
7221
|
-
/* @__PURE__ */ jsx59(
|
|
7222
|
-
Button,
|
|
7223
|
-
{
|
|
7224
|
-
size: "sm",
|
|
7225
|
-
variant: "ghost",
|
|
7226
|
-
disabled: !dirty || saving,
|
|
7227
|
-
onClick: () => {
|
|
7228
|
-
setDraft({ name: user.name || "", title: user.title || "", email: user.email || "", phone: user.phone || "", timezone: user.timezone || TIMEZONES[2], bio: user.bio || "", notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true } });
|
|
7229
|
-
setSaved(false);
|
|
7230
|
-
},
|
|
7231
|
-
children: "Discard"
|
|
7232
|
-
}
|
|
7233
|
-
),
|
|
7234
|
-
/* @__PURE__ */ jsx59(Button, { size: "sm", icon: "check", disabled: !dirty, loading: saving, onClick: save, children: "Save changes" })
|
|
7235
|
-
] })
|
|
7236
|
-
] });
|
|
7237
7533
|
}
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7534
|
+
function useSession() {
|
|
7535
|
+
const [s, setS] = React37.useState(getSession);
|
|
7536
|
+
React37.useEffect(() => subscribe(setS), []);
|
|
7537
|
+
return s;
|
|
7538
|
+
}
|
|
7539
|
+
var SessionKit = {
|
|
7540
|
+
PERMISSION_CATALOG,
|
|
7541
|
+
ALL_PERMISSIONS,
|
|
7542
|
+
ROLES,
|
|
7543
|
+
FLAGS,
|
|
7544
|
+
USERS,
|
|
7545
|
+
loadSession,
|
|
7546
|
+
getSession,
|
|
7547
|
+
setActiveUser,
|
|
7548
|
+
saveProfile,
|
|
7549
|
+
refresh,
|
|
7550
|
+
subscribe,
|
|
7551
|
+
can,
|
|
7552
|
+
canAny,
|
|
7553
|
+
hasRole,
|
|
7554
|
+
flagOn,
|
|
7555
|
+
useSession,
|
|
7556
|
+
findFlag,
|
|
7557
|
+
flagState,
|
|
7558
|
+
isEnabled,
|
|
7559
|
+
flagVisible,
|
|
7560
|
+
flagLive,
|
|
7561
|
+
roadmap,
|
|
7562
|
+
evaluateFlag,
|
|
7563
|
+
explainFlag,
|
|
7564
|
+
bucket,
|
|
7565
|
+
buildSession
|
|
7566
|
+
};
|
|
7567
|
+
function DemoSessionProvider({ children }) {
|
|
7568
|
+
const session = useSession();
|
|
7569
|
+
const value = React37.useMemo(
|
|
7570
|
+
() => ({
|
|
7571
|
+
user: session.user,
|
|
7572
|
+
roles: session.roles,
|
|
7573
|
+
permissions: session.permissions,
|
|
7574
|
+
can,
|
|
7575
|
+
canAny,
|
|
7576
|
+
hasRole,
|
|
7577
|
+
signOut: () => window.alert("Signed out. (Simulated \u2014 no auth provider is wired up.)"),
|
|
7578
|
+
saveProfile: (patch) => {
|
|
7579
|
+
saveProfile(patch);
|
|
7580
|
+
},
|
|
7581
|
+
demo: { allUsers: session.allUsers.filter((u) => u.status === "active"), setActiveUser }
|
|
7582
|
+
}),
|
|
7583
|
+
[session]
|
|
7584
|
+
);
|
|
7585
|
+
return React37.createElement(SessionProvider, { value }, children);
|
|
7586
|
+
}
|
|
7241
7587
|
|
|
7242
7588
|
// src/kits/runtime.ts
|
|
7243
|
-
import * as
|
|
7589
|
+
import * as React38 from "react";
|
|
7244
7590
|
var WIRED_ENDPOINTS = [
|
|
7245
7591
|
// Nothing yet. Every id below would come from a real service:
|
|
7246
7592
|
// "plan.get", "placements.list", …
|
|
@@ -7411,8 +7757,8 @@ var RuntimeKit = {
|
|
|
7411
7757
|
};
|
|
7412
7758
|
RuntimeKit.declare(FEATURE_NEEDS);
|
|
7413
7759
|
function useRuntimeMode() {
|
|
7414
|
-
const [m, setM] =
|
|
7415
|
-
|
|
7760
|
+
const [m, setM] = React38.useState(RuntimeKit.getMode());
|
|
7761
|
+
React38.useEffect(() => RuntimeKit.subscribe(setM), []);
|
|
7416
7762
|
return m;
|
|
7417
7763
|
}
|
|
7418
7764
|
function useFeatureStatus(key) {
|
|
@@ -7431,7 +7777,7 @@ var UseRuntimeMode = useRuntimeMode;
|
|
|
7431
7777
|
var UseFeatureStatus = useFeatureStatus;
|
|
7432
7778
|
|
|
7433
7779
|
// src/components/platform/RoadmapTimeline.tsx
|
|
7434
|
-
import { jsx as
|
|
7780
|
+
import { jsx as jsx62, jsxs as jsxs58 } from "react/jsx-runtime";
|
|
7435
7781
|
var STATUS = {
|
|
7436
7782
|
shipped: { tone: "success", icon: "check-circle", label: "Wired" },
|
|
7437
7783
|
next: { tone: "warning", icon: "traffic-cone", label: "Not wired" },
|
|
@@ -7446,45 +7792,45 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
|
|
|
7446
7792
|
const s = STATUS[item.status] || STATUS.planned;
|
|
7447
7793
|
const deps = (item.dependsOn || []).map((k) => byKey[k]).filter(Boolean);
|
|
7448
7794
|
const blocking = deps.filter((d) => !d.implemented);
|
|
7449
|
-
return /* @__PURE__ */
|
|
7450
|
-
/* @__PURE__ */
|
|
7451
|
-
/* @__PURE__ */
|
|
7452
|
-
/* @__PURE__ */
|
|
7453
|
-
/* @__PURE__ */
|
|
7795
|
+
return /* @__PURE__ */ jsx62(Card, { elevation: "flat", children: /* @__PURE__ */ jsxs58("div", { className: "fd-stack", style: { gap: 10 }, children: [
|
|
7796
|
+
/* @__PURE__ */ jsxs58("div", { className: "fd-row", style: { gap: 8, flexWrap: "wrap" }, children: [
|
|
7797
|
+
/* @__PURE__ */ jsx62("span", { className: "fd-body", style: { fontWeight: 700, flex: 1, minWidth: 140 }, children: item.label }),
|
|
7798
|
+
/* @__PURE__ */ jsx62(Badge, { tone: "neutral", icon: PROJECT_ICON[item.project] || "squares-four", children: item.project }),
|
|
7799
|
+
/* @__PURE__ */ jsx62(Badge, { tone: s.tone, icon: s.icon, children: s.label })
|
|
7454
7800
|
] }),
|
|
7455
|
-
/* @__PURE__ */
|
|
7456
|
-
item.backend ? /* @__PURE__ */
|
|
7457
|
-
/* @__PURE__ */
|
|
7458
|
-
/* @__PURE__ */
|
|
7801
|
+
/* @__PURE__ */ jsx62("p", { className: "fd-body-sm fd-secondary", style: { margin: 0, textWrap: "pretty" }, children: item.description }),
|
|
7802
|
+
item.backend ? /* @__PURE__ */ jsxs58("div", { className: "fd-row", style: { gap: 10, alignItems: "flex-start" }, children: [
|
|
7803
|
+
/* @__PURE__ */ jsx62("span", { className: "fd-overline fd-muted", style: { width: 62, flex: "none", paddingTop: 2 }, children: "Backend" }),
|
|
7804
|
+
/* @__PURE__ */ jsx62("span", { className: "fd-body-sm", style: { flex: 1, textWrap: "pretty" }, children: item.backend })
|
|
7459
7805
|
] }) : null,
|
|
7460
|
-
/* @__PURE__ */
|
|
7461
|
-
item.effort ? /* @__PURE__ */
|
|
7462
|
-
/* @__PURE__ */
|
|
7806
|
+
/* @__PURE__ */ jsxs58("div", { className: "fd-row", style: { gap: 12, flexWrap: "wrap" }, children: [
|
|
7807
|
+
item.effort ? /* @__PURE__ */ jsxs58("span", { className: "fd-body-sm fd-muted", children: [
|
|
7808
|
+
/* @__PURE__ */ jsx62("i", { className: "ph ph-hourglass-medium" }),
|
|
7463
7809
|
" ",
|
|
7464
7810
|
item.effort
|
|
7465
7811
|
] }) : null,
|
|
7466
|
-
/* @__PURE__ */
|
|
7467
|
-
/* @__PURE__ */
|
|
7812
|
+
/* @__PURE__ */ jsxs58("span", { className: "fd-body-sm fd-muted", children: [
|
|
7813
|
+
/* @__PURE__ */ jsx62("i", { className: "ph ph-user" }),
|
|
7468
7814
|
" ",
|
|
7469
7815
|
item.owner
|
|
7470
7816
|
] }),
|
|
7471
|
-
item.screen ? /* @__PURE__ */
|
|
7472
|
-
/* @__PURE__ */
|
|
7473
|
-
/* @__PURE__ */
|
|
7817
|
+
item.screen ? /* @__PURE__ */ jsx62(Badge, { tone: "neutral", icon: "browser", children: "screen" }) : null,
|
|
7818
|
+
/* @__PURE__ */ jsx62("span", { style: { flex: 1 } }),
|
|
7819
|
+
/* @__PURE__ */ jsx62(
|
|
7474
7820
|
"button",
|
|
7475
7821
|
{
|
|
7476
7822
|
type: "button",
|
|
7477
7823
|
onClick: () => onOpenFlag && onOpenFlag(item.key),
|
|
7478
7824
|
style: { all: "unset", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 5 },
|
|
7479
|
-
children: /* @__PURE__ */
|
|
7825
|
+
children: /* @__PURE__ */ jsx62("code", { className: "fd-mono", style: { fontSize: 11, color: "var(--brand)" }, children: item.key })
|
|
7480
7826
|
}
|
|
7481
7827
|
)
|
|
7482
7828
|
] }),
|
|
7483
|
-
deps.length ? /* @__PURE__ */
|
|
7484
|
-
/* @__PURE__ */
|
|
7485
|
-
deps.map((d) => /* @__PURE__ */
|
|
7829
|
+
deps.length ? /* @__PURE__ */ jsxs58("div", { className: "fd-row", style: { gap: 6, flexWrap: "wrap", paddingTop: 6, borderTop: "1px solid var(--border)" }, children: [
|
|
7830
|
+
/* @__PURE__ */ jsx62("span", { className: "fd-overline fd-muted", style: { paddingTop: 3 }, children: "After" }),
|
|
7831
|
+
deps.map((d) => /* @__PURE__ */ jsx62(Tag, { icon: d.implemented ? "check" : "clock", children: d.label }, d.key))
|
|
7486
7832
|
] }) : null,
|
|
7487
|
-
blocking.length && !item.implemented ? /* @__PURE__ */
|
|
7833
|
+
blocking.length && !item.implemented ? /* @__PURE__ */ jsxs58("span", { className: "fd-body-sm", style: { color: "var(--warn-text)" }, children: [
|
|
7488
7834
|
"Blocked until ",
|
|
7489
7835
|
blocking.map((d) => d.label).join(" and "),
|
|
7490
7836
|
" ",
|
|
@@ -7495,12 +7841,12 @@ function RoadmapCard({ item, byKey, onOpenFlag }) {
|
|
|
7495
7841
|
}
|
|
7496
7842
|
function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
|
|
7497
7843
|
const session = SessionKit.useSession();
|
|
7498
|
-
const [project, setProject] =
|
|
7499
|
-
const [q, setQ] =
|
|
7500
|
-
const scrollRef =
|
|
7501
|
-
const nowRef =
|
|
7502
|
-
const rm =
|
|
7503
|
-
const byKey =
|
|
7844
|
+
const [project, setProject] = React39.useState("");
|
|
7845
|
+
const [q, setQ] = React39.useState("");
|
|
7846
|
+
const scrollRef = React39.useRef(null);
|
|
7847
|
+
const nowRef = React39.useRef(null);
|
|
7848
|
+
const rm = React39.useMemo(() => SessionKit.roadmap({ isComplete: RuntimeKit.isComplete }), [session]);
|
|
7849
|
+
const byKey = React39.useMemo(() => {
|
|
7504
7850
|
const m = {};
|
|
7505
7851
|
rm.items.forEach((i) => {
|
|
7506
7852
|
m[i.key] = i;
|
|
@@ -7509,7 +7855,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
|
|
|
7509
7855
|
}, [rm]);
|
|
7510
7856
|
const items = rm.items.filter((i) => (!project || i.project === project) && (!q || (i.label + " " + i.description + " " + (i.backend || "") + " " + i.key).toLowerCase().includes(q.toLowerCase())));
|
|
7511
7857
|
const projects = [...new Set(rm.items.map((i) => i.project))];
|
|
7512
|
-
|
|
7858
|
+
React39.useEffect(() => {
|
|
7513
7859
|
let raf1 = 0, raf2 = 0;
|
|
7514
7860
|
const place = () => {
|
|
7515
7861
|
const box = scrollRef.current, mark = nowRef.current;
|
|
@@ -7537,23 +7883,23 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
|
|
|
7537
7883
|
}, 0);
|
|
7538
7884
|
const nextPhase = items.find((i) => !i.implemented);
|
|
7539
7885
|
let lastPhase = null;
|
|
7540
|
-
return /* @__PURE__ */
|
|
7541
|
-
/* @__PURE__ */
|
|
7542
|
-
/* @__PURE__ */
|
|
7543
|
-
/* @__PURE__ */
|
|
7544
|
-
/* @__PURE__ */
|
|
7886
|
+
return /* @__PURE__ */ jsxs58("div", { className: "fd-stack", style: { gap: 16, maxWidth: 1e3 }, children: [
|
|
7887
|
+
/* @__PURE__ */ jsxs58("div", { className: "fd-stack", style: { gap: 10 }, children: [
|
|
7888
|
+
/* @__PURE__ */ jsx62("span", { className: "fd-overline fd-muted", children: "Architecture" }),
|
|
7889
|
+
/* @__PURE__ */ jsx62("h1", { className: "fd-h1", style: { margin: 0 }, children: title }),
|
|
7890
|
+
/* @__PURE__ */ jsx62("p", { className: "fd-body fd-secondary fd-prose", style: { margin: 0 }, children: lede || "Every screen and feature in these tools is already designed and working against a simulated backend. This is the plan for connecting them to the real one \u2014 so the only work described here is server work." })
|
|
7545
7891
|
] }),
|
|
7546
|
-
/* @__PURE__ */
|
|
7547
|
-
/* @__PURE__ */
|
|
7548
|
-
/* @__PURE__ */
|
|
7549
|
-
/* @__PURE__ */
|
|
7550
|
-
/* @__PURE__ */
|
|
7892
|
+
/* @__PURE__ */ jsxs58("div", { className: "fd-grid-stats is-thin", children: [
|
|
7893
|
+
/* @__PURE__ */ jsx62(StatTile, { compact: true, label: "Wired", value: String(rm.shipped), sub: "of " + rm.items.length + " features" }),
|
|
7894
|
+
/* @__PURE__ */ jsx62(StatTile, { compact: true, label: "Remaining", value: String(rm.remaining), sub: "backend work" }),
|
|
7895
|
+
/* @__PURE__ */ jsx62(StatTile, { compact: true, label: "Est. effort", value: days ? days + " days" : "\u2014", sub: "sum of estimates, not calendar" }),
|
|
7896
|
+
/* @__PURE__ */ jsx62(StatTile, { compact: true, label: "Up next", value: nextPhase ? "Phase " + nextPhase.phase : "\u2014", sub: nextPhase ? nextPhase.phaseName : "all wired" })
|
|
7551
7897
|
] }),
|
|
7552
|
-
/* @__PURE__ */
|
|
7553
|
-
/* @__PURE__ */
|
|
7554
|
-
/* @__PURE__ */
|
|
7555
|
-
/* @__PURE__ */
|
|
7556
|
-
/* @__PURE__ */
|
|
7898
|
+
/* @__PURE__ */ jsxs58("div", { className: "fd-row", style: { gap: 10, flexWrap: "wrap" }, children: [
|
|
7899
|
+
/* @__PURE__ */ jsx62(Input, { icon: "magnifying-glass", placeholder: "Find a feature or a piece of backend work", value: q, onChange: (e) => setQ(e.target.value), style: { width: 300 } }),
|
|
7900
|
+
/* @__PURE__ */ jsx62(Select, { placeholder: "All projects", value: project, onChange: (e) => setProject(e.target.value), options: projects, style: { width: 190 } }),
|
|
7901
|
+
/* @__PURE__ */ jsx62("span", { style: { flex: 1 } }),
|
|
7902
|
+
/* @__PURE__ */ jsx62(
|
|
7557
7903
|
Button,
|
|
7558
7904
|
{
|
|
7559
7905
|
size: "sm",
|
|
@@ -7567,7 +7913,7 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
|
|
|
7567
7913
|
}
|
|
7568
7914
|
)
|
|
7569
7915
|
] }),
|
|
7570
|
-
/* @__PURE__ */
|
|
7916
|
+
/* @__PURE__ */ jsx62(
|
|
7571
7917
|
Flag,
|
|
7572
7918
|
{
|
|
7573
7919
|
tone: "info",
|
|
@@ -7576,60 +7922,60 @@ function RoadmapTimeline({ onOpenFlag, title = "Roadmap", lede }) {
|
|
|
7576
7922
|
actions: null
|
|
7577
7923
|
}
|
|
7578
7924
|
),
|
|
7579
|
-
/* @__PURE__ */
|
|
7580
|
-
/* @__PURE__ */
|
|
7925
|
+
/* @__PURE__ */ jsx62("div", { className: "fd-rm", children: /* @__PURE__ */ jsx62("div", { className: "fd-rm-scroll", ref: scrollRef, children: /* @__PURE__ */ jsxs58("div", { className: "fd-rm-track", children: [
|
|
7926
|
+
/* @__PURE__ */ jsx62("span", { className: "fd-rm-spine", children: /* @__PURE__ */ jsx62("span", { className: "fd-rm-spine-done", style: { height: items.length ? 100 * shippedShown / items.length + "%" : "0%" } }) }),
|
|
7581
7927
|
items.map((item, n) => {
|
|
7582
7928
|
const showPhase = item.phase !== lastPhase;
|
|
7583
7929
|
lastPhase = item.phase;
|
|
7584
7930
|
const inPhase = items.filter((i) => i.phase === item.phase).length;
|
|
7585
7931
|
const isBoundary = n === firstPending;
|
|
7586
|
-
return /* @__PURE__ */
|
|
7587
|
-
showPhase ? /* @__PURE__ */
|
|
7588
|
-
/* @__PURE__ */
|
|
7589
|
-
/* @__PURE__ */
|
|
7590
|
-
item.phase === 0 ? null : /* @__PURE__ */
|
|
7932
|
+
return /* @__PURE__ */ jsxs58(React39.Fragment, { children: [
|
|
7933
|
+
showPhase ? /* @__PURE__ */ jsxs58("div", { className: "fd-rm-era", children: [
|
|
7934
|
+
/* @__PURE__ */ jsxs58("span", { className: "fd-row", style: { gap: 8, flexWrap: "wrap", alignItems: "baseline" }, children: [
|
|
7935
|
+
/* @__PURE__ */ jsx62("span", { style: { fontWeight: 700 }, children: item.phase === 0 ? "Shipped" : "Phase " + item.phase + " \u2014 " + item.phaseName }),
|
|
7936
|
+
item.phase === 0 ? null : /* @__PURE__ */ jsxs58("span", { className: "fd-mono", style: { opacity: 0.7, fontWeight: 400 }, children: [
|
|
7591
7937
|
fmtDate(item.phaseStart),
|
|
7592
7938
|
" \u2013 ",
|
|
7593
7939
|
fmtDate(item.date)
|
|
7594
7940
|
] }),
|
|
7595
|
-
/* @__PURE__ */
|
|
7941
|
+
/* @__PURE__ */ jsxs58("span", { style: { opacity: 0.7, fontWeight: 400 }, children: [
|
|
7596
7942
|
"\xB7 ",
|
|
7597
7943
|
inPhase,
|
|
7598
7944
|
" feature",
|
|
7599
7945
|
inPhase === 1 ? "" : "s"
|
|
7600
7946
|
] })
|
|
7601
7947
|
] }),
|
|
7602
|
-
item.phaseWhy ? /* @__PURE__ */
|
|
7948
|
+
item.phaseWhy ? /* @__PURE__ */ jsx62("p", { className: "fd-rm-era-why", children: item.phaseWhy }) : null
|
|
7603
7949
|
] }) : null,
|
|
7604
|
-
isBoundary ? /* @__PURE__ */
|
|
7605
|
-
/* @__PURE__ */
|
|
7950
|
+
isBoundary ? /* @__PURE__ */ jsx62("div", { className: "fd-rm-now", ref: nowRef, children: /* @__PURE__ */ jsxs58("span", { className: "fd-rm-now-pill", children: [
|
|
7951
|
+
/* @__PURE__ */ jsx62("i", { className: "ph ph-map-pin" }),
|
|
7606
7952
|
" You are here \u2014 everything above is wired"
|
|
7607
7953
|
] }) }) : null,
|
|
7608
|
-
/* @__PURE__ */
|
|
7609
|
-
/* @__PURE__ */
|
|
7610
|
-
/* @__PURE__ */
|
|
7954
|
+
/* @__PURE__ */ jsxs58("div", { className: "fd-rm-row", children: [
|
|
7955
|
+
/* @__PURE__ */ jsx62("span", { className: "fd-rm-dot " + (item.implemented ? "is-shipped" : item.status === "next" ? "is-next" : "") }),
|
|
7956
|
+
/* @__PURE__ */ jsxs58("div", { className: "fd-rm-date", children: [
|
|
7611
7957
|
fmtDate(item.date),
|
|
7612
|
-
/* @__PURE__ */
|
|
7613
|
-
/* @__PURE__ */
|
|
7958
|
+
/* @__PURE__ */ jsx62("br", {}),
|
|
7959
|
+
/* @__PURE__ */ jsx62("span", { style: { opacity: 0.75 }, children: item.implemented ? "shipped" : "phase " + item.phase })
|
|
7614
7960
|
] }),
|
|
7615
|
-
/* @__PURE__ */
|
|
7961
|
+
/* @__PURE__ */ jsx62(RoadmapCard, { item, byKey, onOpenFlag })
|
|
7616
7962
|
] })
|
|
7617
7963
|
] }, item.key);
|
|
7618
7964
|
}),
|
|
7619
|
-
!items.length ? /* @__PURE__ */
|
|
7965
|
+
!items.length ? /* @__PURE__ */ jsx62("p", { className: "fd-body-sm fd-muted", children: "Nothing matches that filter." }) : null
|
|
7620
7966
|
] }) }) }),
|
|
7621
|
-
/* @__PURE__ */
|
|
7967
|
+
/* @__PURE__ */ jsxs58("p", { className: "fd-body-sm fd-muted", style: { margin: 0 }, children: [
|
|
7622
7968
|
"Derived from the feature-flag registry \u2014 each entry's status is its flag's ",
|
|
7623
|
-
/* @__PURE__ */
|
|
7969
|
+
/* @__PURE__ */ jsx62("code", { className: "fd-mono", children: "implemented" }),
|
|
7624
7970
|
" field, so this page cannot drift from what the apps actually do."
|
|
7625
7971
|
] })
|
|
7626
7972
|
] });
|
|
7627
7973
|
}
|
|
7628
7974
|
|
|
7629
7975
|
// src/components/platform/ComingSoon.tsx
|
|
7630
|
-
import * as
|
|
7976
|
+
import * as React40 from "react";
|
|
7631
7977
|
import { createPortal as createPortal5 } from "react-dom";
|
|
7632
|
-
import { Fragment as
|
|
7978
|
+
import { Fragment as Fragment18, jsx as jsx63, jsxs as jsxs59 } from "react/jsx-runtime";
|
|
7633
7979
|
var BYPASS_STORE = "fd.soon.bypass.v1";
|
|
7634
7980
|
function readBypassed() {
|
|
7635
7981
|
try {
|
|
@@ -7645,8 +7991,8 @@ function writeBypassed(list) {
|
|
|
7645
7991
|
}
|
|
7646
7992
|
}
|
|
7647
7993
|
function useBypass(key) {
|
|
7648
|
-
const [on, setOn] =
|
|
7649
|
-
|
|
7994
|
+
const [on, setOn] = React40.useState(() => !!key && readBypassed().indexOf(key) >= 0);
|
|
7995
|
+
React40.useEffect(() => {
|
|
7650
7996
|
setOn(!!key && readBypassed().indexOf(key) >= 0);
|
|
7651
7997
|
}, [key]);
|
|
7652
7998
|
const set = (next) => {
|
|
@@ -7659,43 +8005,43 @@ function useBypass(key) {
|
|
|
7659
8005
|
return [on, set];
|
|
7660
8006
|
}
|
|
7661
8007
|
function SoonCard({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass }) {
|
|
7662
|
-
return /* @__PURE__ */
|
|
7663
|
-
/* @__PURE__ */
|
|
7664
|
-
/* @__PURE__ */
|
|
8008
|
+
return /* @__PURE__ */ jsxs59(Fragment18, { children: [
|
|
8009
|
+
/* @__PURE__ */ jsxs59("span", { className: "fd-soon-badge", children: [
|
|
8010
|
+
/* @__PURE__ */ jsx63("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" }),
|
|
7665
8011
|
label
|
|
7666
8012
|
] }),
|
|
7667
|
-
detail ? /* @__PURE__ */
|
|
7668
|
-
backend ? /* @__PURE__ */
|
|
7669
|
-
/* @__PURE__ */
|
|
8013
|
+
detail ? /* @__PURE__ */ jsx63("span", { className: "fd-soon-detail", children: detail }) : null,
|
|
8014
|
+
backend ? /* @__PURE__ */ jsxs59("span", { className: "fd-soon-backend", children: [
|
|
8015
|
+
/* @__PURE__ */ jsx63("span", { className: "fd-overline", children: "Needs" }),
|
|
7670
8016
|
" ",
|
|
7671
8017
|
backend
|
|
7672
8018
|
] }) : null,
|
|
7673
|
-
eta || effort || onRoadmap ? /* @__PURE__ */
|
|
7674
|
-
eta ? /* @__PURE__ */
|
|
7675
|
-
/* @__PURE__ */
|
|
8019
|
+
eta || effort || onRoadmap ? /* @__PURE__ */ jsxs59("span", { className: "fd-soon-meta", children: [
|
|
8020
|
+
eta ? /* @__PURE__ */ jsxs59("span", { children: [
|
|
8021
|
+
/* @__PURE__ */ jsx63("i", { className: "ph ph-calendar-blank", "aria-hidden": "true" }),
|
|
7676
8022
|
" ",
|
|
7677
8023
|
eta
|
|
7678
8024
|
] }) : null,
|
|
7679
|
-
effort ? /* @__PURE__ */
|
|
7680
|
-
/* @__PURE__ */
|
|
8025
|
+
effort ? /* @__PURE__ */ jsxs59("span", { children: [
|
|
8026
|
+
/* @__PURE__ */ jsx63("i", { className: "ph ph-hourglass-medium", "aria-hidden": "true" }),
|
|
7681
8027
|
" ",
|
|
7682
8028
|
effort
|
|
7683
8029
|
] }) : null,
|
|
7684
|
-
onRoadmap ? /* @__PURE__ */
|
|
8030
|
+
onRoadmap ? /* @__PURE__ */ jsxs59("button", { type: "button", className: "fd-soon-link", onClick: onRoadmap, children: [
|
|
7685
8031
|
"See the roadmap ",
|
|
7686
|
-
/* @__PURE__ */
|
|
8032
|
+
/* @__PURE__ */ jsx63("i", { className: "ph ph-arrow-right", "aria-hidden": "true" })
|
|
7687
8033
|
] }) : null
|
|
7688
8034
|
] }) : null,
|
|
7689
|
-
allowed ? /* @__PURE__ */
|
|
7690
|
-
/* @__PURE__ */
|
|
8035
|
+
allowed ? /* @__PURE__ */ jsxs59("button", { type: "button", className: "fd-soon-view", onClick: onBypass, children: [
|
|
8036
|
+
/* @__PURE__ */ jsx63("i", { className: "ph ph-eye", "aria-hidden": "true" }),
|
|
7691
8037
|
" View and use it anyway"
|
|
7692
8038
|
] }) : null
|
|
7693
8039
|
] });
|
|
7694
8040
|
}
|
|
7695
8041
|
function useHoverCard(open) {
|
|
7696
|
-
const anchor =
|
|
7697
|
-
const [pos, setPos] =
|
|
7698
|
-
|
|
8042
|
+
const anchor = React40.useRef(null);
|
|
8043
|
+
const [pos, setPos] = React40.useState(null);
|
|
8044
|
+
React40.useLayoutEffect(() => {
|
|
7699
8045
|
if (!open || !anchor.current) {
|
|
7700
8046
|
setPos(null);
|
|
7701
8047
|
return;
|
|
@@ -7747,9 +8093,9 @@ function ComingSoon({
|
|
|
7747
8093
|
const tip = [label, detail, backend ? "Needs " + backend : null, eta ? "ETA " + eta : null, effort].filter(Boolean).join(" \xB7 ");
|
|
7748
8094
|
if (inline) {
|
|
7749
8095
|
if (allowed && bypassed) {
|
|
7750
|
-
return /* @__PURE__ */
|
|
8096
|
+
return /* @__PURE__ */ jsxs59("span", { className: ["fd-soon-inline-on", className].filter(Boolean).join(" "), ...rest, children: [
|
|
7751
8097
|
children,
|
|
7752
|
-
/* @__PURE__ */
|
|
8098
|
+
/* @__PURE__ */ jsx63(
|
|
7753
8099
|
"button",
|
|
7754
8100
|
{
|
|
7755
8101
|
type: "button",
|
|
@@ -7757,12 +8103,12 @@ function ComingSoon({
|
|
|
7757
8103
|
title: "Unwired \u2014 writes go to the simulated backend. " + tip + " Click to re-blur.",
|
|
7758
8104
|
onClick: () => setBypassed(false),
|
|
7759
8105
|
"aria-label": "Re-blur this unwired feature",
|
|
7760
|
-
children: /* @__PURE__ */
|
|
8106
|
+
children: /* @__PURE__ */ jsx63("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" })
|
|
7761
8107
|
}
|
|
7762
8108
|
)
|
|
7763
8109
|
] });
|
|
7764
8110
|
}
|
|
7765
|
-
return /* @__PURE__ */
|
|
8111
|
+
return /* @__PURE__ */ jsx63(
|
|
7766
8112
|
InlineSoon,
|
|
7767
8113
|
{
|
|
7768
8114
|
label,
|
|
@@ -7782,20 +8128,20 @@ function ComingSoon({
|
|
|
7782
8128
|
);
|
|
7783
8129
|
}
|
|
7784
8130
|
if (allowed && bypassed) {
|
|
7785
|
-
return /* @__PURE__ */
|
|
7786
|
-
/* @__PURE__ */
|
|
7787
|
-
/* @__PURE__ */
|
|
7788
|
-
/* @__PURE__ */
|
|
8131
|
+
return /* @__PURE__ */ jsxs59("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 14 }, ...rest, children: [
|
|
8132
|
+
/* @__PURE__ */ jsxs59("div", { className: "fd-soon-bar", role: "status", children: [
|
|
8133
|
+
/* @__PURE__ */ jsxs59("span", { className: "fd-soon-badge", children: [
|
|
8134
|
+
/* @__PURE__ */ jsx63("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" }),
|
|
7789
8135
|
"Unwired feature \u2014 you are using it anyway"
|
|
7790
8136
|
] }),
|
|
7791
|
-
/* @__PURE__ */
|
|
7792
|
-
/* @__PURE__ */
|
|
7793
|
-
onRoadmap ? /* @__PURE__ */
|
|
8137
|
+
/* @__PURE__ */ jsx63("span", { className: "fd-soon-bar-detail", children: "Every action here writes to the simulated backend, so nothing you do persists beyond this session." }),
|
|
8138
|
+
/* @__PURE__ */ jsxs59("span", { className: "fd-soon-bar-actions", children: [
|
|
8139
|
+
onRoadmap ? /* @__PURE__ */ jsxs59("button", { type: "button", className: "fd-soon-link", onClick: onRoadmap, children: [
|
|
7794
8140
|
"Roadmap ",
|
|
7795
|
-
/* @__PURE__ */
|
|
8141
|
+
/* @__PURE__ */ jsx63("i", { className: "ph ph-arrow-right", "aria-hidden": "true" })
|
|
7796
8142
|
] }) : null,
|
|
7797
|
-
/* @__PURE__ */
|
|
7798
|
-
/* @__PURE__ */
|
|
8143
|
+
/* @__PURE__ */ jsxs59("button", { type: "button", className: "fd-soon-link", onClick: () => setBypassed(false), children: [
|
|
8144
|
+
/* @__PURE__ */ jsx63("i", { className: "ph ph-eye-slash", "aria-hidden": "true" }),
|
|
7799
8145
|
" Re-blur"
|
|
7800
8146
|
] })
|
|
7801
8147
|
] })
|
|
@@ -7803,10 +8149,10 @@ function ComingSoon({
|
|
|
7803
8149
|
children
|
|
7804
8150
|
] });
|
|
7805
8151
|
}
|
|
7806
|
-
return /* @__PURE__ */
|
|
7807
|
-
/* @__PURE__ */
|
|
7808
|
-
/* @__PURE__ */
|
|
7809
|
-
/* @__PURE__ */
|
|
8152
|
+
return /* @__PURE__ */ jsxs59("div", { className: ["fd-soon", className].filter(Boolean).join(" "), style: minHeight ? { minHeight } : void 0, ...rest, children: [
|
|
8153
|
+
/* @__PURE__ */ jsx63("div", { className: "fd-soon-under", style: { filter: "blur(" + blur + "px) saturate(.62)" }, ...{ inert: "" }, "aria-hidden": "true", children }),
|
|
8154
|
+
/* @__PURE__ */ jsx63("div", { className: "fd-soon-veil" }),
|
|
8155
|
+
/* @__PURE__ */ jsx63("div", { className: "fd-soon-note", role: "note", children: /* @__PURE__ */ jsx63(
|
|
7810
8156
|
SoonCard,
|
|
7811
8157
|
{
|
|
7812
8158
|
label,
|
|
@@ -7822,10 +8168,10 @@ function ComingSoon({
|
|
|
7822
8168
|
] });
|
|
7823
8169
|
}
|
|
7824
8170
|
function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, onBypass, blur, tip, className, rest, children }) {
|
|
7825
|
-
const [open, setOpen] =
|
|
8171
|
+
const [open, setOpen] = React40.useState(false);
|
|
7826
8172
|
const [anchor, pos] = useHoverCard(open);
|
|
7827
8173
|
const zIndex = useOverlayLayer(open);
|
|
7828
|
-
const close =
|
|
8174
|
+
const close = React40.useRef(null);
|
|
7829
8175
|
const show = () => {
|
|
7830
8176
|
if (close.current) {
|
|
7831
8177
|
clearTimeout(close.current);
|
|
@@ -7841,10 +8187,10 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
|
|
|
7841
8187
|
setOpen(false);
|
|
7842
8188
|
}, 140);
|
|
7843
8189
|
};
|
|
7844
|
-
|
|
8190
|
+
React40.useEffect(() => () => {
|
|
7845
8191
|
if (close.current) clearTimeout(close.current);
|
|
7846
8192
|
}, []);
|
|
7847
|
-
return /* @__PURE__ */
|
|
8193
|
+
return /* @__PURE__ */ jsxs59(
|
|
7848
8194
|
"span",
|
|
7849
8195
|
{
|
|
7850
8196
|
ref: anchor,
|
|
@@ -7855,8 +8201,8 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
|
|
|
7855
8201
|
onBlur: hide,
|
|
7856
8202
|
...rest,
|
|
7857
8203
|
children: [
|
|
7858
|
-
/* @__PURE__ */
|
|
7859
|
-
/* @__PURE__ */
|
|
8204
|
+
/* @__PURE__ */ jsx63("span", { className: "fd-soon-under", style: { filter: "blur(" + Math.min(blur, 1.1) + "px) saturate(.66)" }, ...{ inert: "" }, "aria-hidden": "true", children }),
|
|
8205
|
+
/* @__PURE__ */ jsx63(
|
|
7860
8206
|
"button",
|
|
7861
8207
|
{
|
|
7862
8208
|
type: "button",
|
|
@@ -7867,11 +8213,11 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
|
|
|
7867
8213
|
onFocus: show,
|
|
7868
8214
|
onBlur: hide,
|
|
7869
8215
|
onClick: () => open ? setOpen(false) : show(),
|
|
7870
|
-
children: /* @__PURE__ */
|
|
8216
|
+
children: /* @__PURE__ */ jsx63("i", { className: "ph ph-traffic-cone", "aria-hidden": "true" })
|
|
7871
8217
|
}
|
|
7872
8218
|
),
|
|
7873
8219
|
open && pos ? createPortal5(
|
|
7874
|
-
/* @__PURE__ */
|
|
8220
|
+
/* @__PURE__ */ jsx63(LayerProvider, { zIndex, children: /* @__PURE__ */ jsx63(
|
|
7875
8221
|
"div",
|
|
7876
8222
|
{
|
|
7877
8223
|
className: "fd-soon-note fd-soon-hovercard",
|
|
@@ -7879,7 +8225,7 @@ function InlineSoon({ label, detail, backend, eta, effort, onRoadmap, allowed, o
|
|
|
7879
8225
|
style: { position: "fixed", left: pos.left, top: pos.top, bottom: pos.bottom, width: pos.width, zIndex },
|
|
7880
8226
|
onMouseEnter: show,
|
|
7881
8227
|
onMouseLeave: hide,
|
|
7882
|
-
children: /* @__PURE__ */
|
|
8228
|
+
children: /* @__PURE__ */ jsx63(
|
|
7883
8229
|
SoonCard,
|
|
7884
8230
|
{
|
|
7885
8231
|
label,
|
|
@@ -7909,10 +8255,10 @@ function formatEta(iso2) {
|
|
|
7909
8255
|
var FormatEta = formatEta;
|
|
7910
8256
|
|
|
7911
8257
|
// src/components/platform/Gate.tsx
|
|
7912
|
-
import { jsx as
|
|
8258
|
+
import { jsx as jsx64, jsxs as jsxs60 } from "react/jsx-runtime";
|
|
7913
8259
|
function PermissionDenied({ permission, title, detail, compact: compact3 = false, className = "", ...rest }) {
|
|
7914
8260
|
const need = Array.isArray(permission) ? permission : [permission].filter(Boolean);
|
|
7915
|
-
return /* @__PURE__ */
|
|
8261
|
+
return /* @__PURE__ */ jsxs60(
|
|
7916
8262
|
"div",
|
|
7917
8263
|
{
|
|
7918
8264
|
className: ["fd-stack", className].filter(Boolean).join(" "),
|
|
@@ -7928,14 +8274,14 @@ function PermissionDenied({ permission, title, detail, compact: compact3 = false
|
|
|
7928
8274
|
},
|
|
7929
8275
|
...rest,
|
|
7930
8276
|
children: [
|
|
7931
|
-
/* @__PURE__ */
|
|
7932
|
-
/* @__PURE__ */
|
|
7933
|
-
/* @__PURE__ */
|
|
8277
|
+
/* @__PURE__ */ jsxs60("span", { className: "fd-row", style: { gap: 8 }, children: [
|
|
8278
|
+
/* @__PURE__ */ jsx64("i", { className: "ph ph-lock-simple", style: { fontSize: 16, color: "var(--text-muted)" }, "aria-hidden": "true" }),
|
|
8279
|
+
/* @__PURE__ */ jsx64("span", { className: compact3 ? "fd-label-lg" : "fd-h3", children: title || "You do not have access to this" })
|
|
7934
8280
|
] }),
|
|
7935
|
-
/* @__PURE__ */
|
|
7936
|
-
need.length ? /* @__PURE__ */
|
|
7937
|
-
/* @__PURE__ */
|
|
7938
|
-
need.map((p) => /* @__PURE__ */
|
|
8281
|
+
/* @__PURE__ */ jsx64("p", { className: "fd-body-sm fd-secondary", style: { margin: 0, textWrap: "pretty" }, children: detail || "Your roles do not grant this. An administrator can change that from Admin \u2192 Users." }),
|
|
8282
|
+
need.length ? /* @__PURE__ */ jsxs60("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap" }, children: [
|
|
8283
|
+
/* @__PURE__ */ jsx64("span", { className: "fd-overline fd-muted", children: "Requires" }),
|
|
8284
|
+
need.map((p) => /* @__PURE__ */ jsx64("code", { className: "fd-mono", style: {
|
|
7939
8285
|
fontSize: 11.5,
|
|
7940
8286
|
padding: "2px 7px",
|
|
7941
8287
|
borderRadius: 5,
|
|
@@ -7948,15 +8294,15 @@ function PermissionDenied({ permission, title, detail, compact: compact3 = false
|
|
|
7948
8294
|
);
|
|
7949
8295
|
}
|
|
7950
8296
|
function Gate({ perm, anyOf, role, silent = false, fallback, compact: compact3 = false, children }) {
|
|
7951
|
-
|
|
8297
|
+
const { can: can2, canAny: canAny2, hasRole: hasRole2 } = useSessionContext();
|
|
7952
8298
|
let ok = true;
|
|
7953
|
-
if (perm) ok = ok &&
|
|
7954
|
-
if (anyOf) ok = ok &&
|
|
7955
|
-
if (role) ok = ok && (Array.isArray(role) ? role.some(
|
|
8299
|
+
if (perm) ok = ok && can2(perm);
|
|
8300
|
+
if (anyOf) ok = ok && canAny2(anyOf);
|
|
8301
|
+
if (role) ok = ok && (Array.isArray(role) ? role.some(hasRole2) : hasRole2(role));
|
|
7956
8302
|
if (ok) return children;
|
|
7957
8303
|
if (fallback !== void 0) return fallback;
|
|
7958
8304
|
if (silent) return null;
|
|
7959
|
-
return /* @__PURE__ */
|
|
8305
|
+
return /* @__PURE__ */ jsx64(PermissionDenied, { permission: perm || anyOf, compact: compact3 });
|
|
7960
8306
|
}
|
|
7961
8307
|
function FeatureGate({
|
|
7962
8308
|
flag,
|
|
@@ -7971,14 +8317,15 @@ function FeatureGate({
|
|
|
7971
8317
|
bypassPerm = "flags.manage"
|
|
7972
8318
|
}) {
|
|
7973
8319
|
SessionKit.useSession();
|
|
8320
|
+
const { can: can2 } = useSessionContext();
|
|
7974
8321
|
const rt = useFeatureStatus(flag);
|
|
7975
8322
|
if (!SessionKit.isEnabled(flag)) return fallback;
|
|
7976
8323
|
if (rt.complete || rt.mode === "test") return children;
|
|
7977
8324
|
if (!preview) return fallback;
|
|
7978
8325
|
const f = SessionKit.findFlag(flag) || {};
|
|
7979
8326
|
const missing = rt.missing || [];
|
|
7980
|
-
const canBypass = bypassPerm === null ? false : !bypassPerm ||
|
|
7981
|
-
return /* @__PURE__ */
|
|
8327
|
+
const canBypass = bypassPerm === null ? false : !bypassPerm || can2(bypassPerm);
|
|
8328
|
+
return /* @__PURE__ */ jsx64(
|
|
7982
8329
|
ComingSoon,
|
|
7983
8330
|
{
|
|
7984
8331
|
label: label || (variant === "inline" ? f.label || "Not wired yet" : "Designed \u2014 backend not wired yet"),
|
|
@@ -7997,28 +8344,28 @@ function FeatureGate({
|
|
|
7997
8344
|
);
|
|
7998
8345
|
}
|
|
7999
8346
|
function PermissionHint({ perm, children }) {
|
|
8000
|
-
|
|
8001
|
-
if (
|
|
8002
|
-
return /* @__PURE__ */
|
|
8347
|
+
const { can: can2 } = useSessionContext();
|
|
8348
|
+
if (can2(perm)) return children;
|
|
8349
|
+
return /* @__PURE__ */ jsx64(
|
|
8003
8350
|
"span",
|
|
8004
8351
|
{
|
|
8005
8352
|
title: "Requires " + (Array.isArray(perm) ? perm.join(", ") : perm),
|
|
8006
8353
|
style: { display: "inline-flex", opacity: 0.45, cursor: "not-allowed" },
|
|
8007
8354
|
"aria-disabled": "true",
|
|
8008
|
-
children: /* @__PURE__ */
|
|
8355
|
+
children: /* @__PURE__ */ jsx64("span", { style: { pointerEvents: "none" }, children })
|
|
8009
8356
|
}
|
|
8010
8357
|
);
|
|
8011
8358
|
}
|
|
8012
8359
|
|
|
8013
8360
|
// src/components/platform/ModeSwitch.tsx
|
|
8014
|
-
import * as
|
|
8015
|
-
import { Fragment as
|
|
8361
|
+
import * as React41 from "react";
|
|
8362
|
+
import { Fragment as Fragment19, jsx as jsx65, jsxs as jsxs61 } from "react/jsx-runtime";
|
|
8016
8363
|
function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog, onOpenSpec, summary }) {
|
|
8017
8364
|
const mode2 = useRuntimeMode();
|
|
8018
|
-
const [open, setOpen] =
|
|
8365
|
+
const [open, setOpen] = React41.useState(false);
|
|
8019
8366
|
const zIndex = useOverlayLayer(open);
|
|
8020
|
-
const ref =
|
|
8021
|
-
|
|
8367
|
+
const ref = React41.useRef(null);
|
|
8368
|
+
React41.useEffect(() => {
|
|
8022
8369
|
if (!open) return;
|
|
8023
8370
|
const away = (e) => {
|
|
8024
8371
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
@@ -8040,8 +8387,8 @@ function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog
|
|
|
8040
8387
|
RuntimeKit.setMode(next);
|
|
8041
8388
|
setOpen(false);
|
|
8042
8389
|
};
|
|
8043
|
-
return /* @__PURE__ */
|
|
8044
|
-
/* @__PURE__ */
|
|
8390
|
+
return /* @__PURE__ */ jsxs61("span", { style: { position: "relative", display: "inline-flex" }, ref, children: [
|
|
8391
|
+
/* @__PURE__ */ jsxs61(
|
|
8045
8392
|
"button",
|
|
8046
8393
|
{
|
|
8047
8394
|
type: "button",
|
|
@@ -8051,29 +8398,29 @@ function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog
|
|
|
8051
8398
|
title: test ? "Test mode \u2014 every response is simulated. Click to inspect requests or return to live mode." : "Live mode \u2014 incomplete features are shown under construction. Click for test mode.",
|
|
8052
8399
|
className: "fd-mode-btn" + (test ? " is-test" : "") + (open ? " is-open" : ""),
|
|
8053
8400
|
children: [
|
|
8054
|
-
/* @__PURE__ */
|
|
8055
|
-
test && requestCount ? /* @__PURE__ */
|
|
8401
|
+
/* @__PURE__ */ jsx65("i", { className: "ph " + (test ? "ph-flask" : "ph-lightning"), style: { fontSize: 17 } }),
|
|
8402
|
+
test && requestCount ? /* @__PURE__ */ jsx65("span", { className: "fd-mono fd-mode-count", children: requestCount > 99 ? "99+" : requestCount }, requestCount) : null
|
|
8056
8403
|
]
|
|
8057
8404
|
}
|
|
8058
8405
|
),
|
|
8059
|
-
open ? /* @__PURE__ */
|
|
8060
|
-
/* @__PURE__ */
|
|
8061
|
-
/* @__PURE__ */
|
|
8062
|
-
/* @__PURE__ */
|
|
8406
|
+
open ? /* @__PURE__ */ jsx65(LayerProvider, { zIndex, children: /* @__PURE__ */ jsxs61("span", { className: "fd-view-enter fd-mode-pop", style: { zIndex }, children: [
|
|
8407
|
+
/* @__PURE__ */ jsxs61("span", { className: "fd-row", style: { gap: 10, padding: "12px 14px", borderBottom: "1px solid var(--border)" }, children: [
|
|
8408
|
+
/* @__PURE__ */ jsxs61("span", { className: "fd-badge " + (test ? "fd-badge-warning" : "fd-badge-neutral"), children: [
|
|
8409
|
+
/* @__PURE__ */ jsx65("i", { className: "ph " + (test ? "ph-flask" : "ph-lightning"), "aria-hidden": "true" }),
|
|
8063
8410
|
test ? "Test mode" : "Live mode"
|
|
8064
8411
|
] }),
|
|
8065
|
-
test ? /* @__PURE__ */
|
|
8412
|
+
test ? /* @__PURE__ */ jsxs61("span", { className: "fd-body-sm fd-muted fd-mono", children: [
|
|
8066
8413
|
requestCount,
|
|
8067
8414
|
" simulated request",
|
|
8068
8415
|
requestCount === 1 ? "" : "s"
|
|
8069
8416
|
] }) : null,
|
|
8070
|
-
/* @__PURE__ */
|
|
8071
|
-
test && onClearLog ? /* @__PURE__ */
|
|
8417
|
+
/* @__PURE__ */ jsx65("span", { style: { flex: 1 } }),
|
|
8418
|
+
test && onClearLog ? /* @__PURE__ */ jsx65("button", { type: "button", className: "fd-soon-link", onClick: onClearLog, children: "Clear" }) : null
|
|
8072
8419
|
] }),
|
|
8073
|
-
/* @__PURE__ */
|
|
8074
|
-
/* @__PURE__ */
|
|
8075
|
-
/* @__PURE__ */
|
|
8076
|
-
/* @__PURE__ */
|
|
8420
|
+
/* @__PURE__ */ jsxs61("span", { style: { display: "block", padding: "12px 14px", borderBottom: "1px solid var(--border)" }, children: [
|
|
8421
|
+
/* @__PURE__ */ jsx65("span", { className: "fd-body-sm fd-secondary", style: { display: "block", textWrap: "pretty" }, children: test ? "Every feature is usable and nothing is obstructed, but no response comes from a real service \u2014 nothing you do here persists. Use this to review and demo the design." : "This is what a user would see today. " + s.incomplete + " of " + s.total + " features depend on backend work that is not wired yet, so they render under construction." }),
|
|
8422
|
+
/* @__PURE__ */ jsxs61("span", { className: "fd-row", style: { gap: 8, marginTop: 10, flexWrap: "wrap" }, children: [
|
|
8423
|
+
/* @__PURE__ */ jsxs61("span", { className: "fd-body-sm fd-muted fd-mono", children: [
|
|
8077
8424
|
s.endpointsWired,
|
|
8078
8425
|
" endpoint",
|
|
8079
8426
|
s.endpointsWired === 1 ? "" : "s",
|
|
@@ -8083,58 +8430,58 @@ function ModeSwitch({ canToggle = false, requestCount = 0, onClearLog, renderLog
|
|
|
8083
8430
|
s.total,
|
|
8084
8431
|
" features complete"
|
|
8085
8432
|
] }),
|
|
8086
|
-
onOpenSpec ? /* @__PURE__ */
|
|
8087
|
-
/* @__PURE__ */
|
|
8088
|
-
/* @__PURE__ */
|
|
8433
|
+
onOpenSpec ? /* @__PURE__ */ jsxs61(Fragment19, { children: [
|
|
8434
|
+
/* @__PURE__ */ jsx65("span", { style: { flex: 1 } }),
|
|
8435
|
+
/* @__PURE__ */ jsxs61("button", { type: "button", className: "fd-soon-link", onClick: () => {
|
|
8089
8436
|
setOpen(false);
|
|
8090
8437
|
onOpenSpec();
|
|
8091
8438
|
}, children: [
|
|
8092
8439
|
"API spec ",
|
|
8093
|
-
/* @__PURE__ */
|
|
8440
|
+
/* @__PURE__ */ jsx65("i", { className: "ph ph-arrow-right", "aria-hidden": "true" })
|
|
8094
8441
|
] })
|
|
8095
8442
|
] }) : null
|
|
8096
8443
|
] })
|
|
8097
8444
|
] }),
|
|
8098
|
-
/* @__PURE__ */
|
|
8099
|
-
/* @__PURE__ */
|
|
8100
|
-
/* @__PURE__ */
|
|
8101
|
-
/* @__PURE__ */
|
|
8102
|
-
/* @__PURE__ */
|
|
8103
|
-
/* @__PURE__ */
|
|
8445
|
+
/* @__PURE__ */ jsxs61("span", { className: "fd-mode-choice", children: [
|
|
8446
|
+
/* @__PURE__ */ jsxs61("button", { type: "button", className: "fd-mode-opt" + (!test ? " is-on" : ""), onClick: () => go("live"), children: [
|
|
8447
|
+
/* @__PURE__ */ jsx65("i", { className: "ph ph-lightning", "aria-hidden": "true" }),
|
|
8448
|
+
/* @__PURE__ */ jsxs61("span", { className: "fd-mode-opt-text", children: [
|
|
8449
|
+
/* @__PURE__ */ jsx65("span", { className: "fd-mode-opt-title", children: "Live mode" }),
|
|
8450
|
+
/* @__PURE__ */ jsx65("span", { className: "fd-mode-opt-desc", children: "Incomplete features under construction" })
|
|
8104
8451
|
] }),
|
|
8105
|
-
!test ? /* @__PURE__ */
|
|
8452
|
+
!test ? /* @__PURE__ */ jsx65("i", { className: "ph ph-check", "aria-hidden": "true" }) : null
|
|
8106
8453
|
] }),
|
|
8107
|
-
/* @__PURE__ */
|
|
8108
|
-
/* @__PURE__ */
|
|
8109
|
-
/* @__PURE__ */
|
|
8110
|
-
/* @__PURE__ */
|
|
8111
|
-
/* @__PURE__ */
|
|
8454
|
+
/* @__PURE__ */ jsxs61("button", { type: "button", className: "fd-mode-opt" + (test ? " is-on" : ""), onClick: () => go("test"), children: [
|
|
8455
|
+
/* @__PURE__ */ jsx65("i", { className: "ph ph-flask", "aria-hidden": "true" }),
|
|
8456
|
+
/* @__PURE__ */ jsxs61("span", { className: "fd-mode-opt-text", children: [
|
|
8457
|
+
/* @__PURE__ */ jsx65("span", { className: "fd-mode-opt-title", children: "Test mode" }),
|
|
8458
|
+
/* @__PURE__ */ jsx65("span", { className: "fd-mode-opt-desc", children: "Everything usable, all data simulated" })
|
|
8112
8459
|
] }),
|
|
8113
|
-
test ? /* @__PURE__ */
|
|
8460
|
+
test ? /* @__PURE__ */ jsx65("i", { className: "ph ph-check", "aria-hidden": "true" }) : null
|
|
8114
8461
|
] })
|
|
8115
8462
|
] }),
|
|
8116
|
-
test && renderLog ? /* @__PURE__ */
|
|
8463
|
+
test && renderLog ? /* @__PURE__ */ jsx65("span", { style: { display: "block", maxHeight: 340, overflowY: "auto", borderTop: "1px solid var(--border)" }, children: renderLog() }) : null
|
|
8117
8464
|
] }) }) : null
|
|
8118
8465
|
] });
|
|
8119
8466
|
}
|
|
8120
8467
|
function TestModeBar({ onExit }) {
|
|
8121
8468
|
const mode2 = useRuntimeMode();
|
|
8122
8469
|
if (mode2 !== "test") return null;
|
|
8123
|
-
return /* @__PURE__ */
|
|
8124
|
-
/* @__PURE__ */
|
|
8125
|
-
/* @__PURE__ */
|
|
8470
|
+
return /* @__PURE__ */ jsxs61("div", { className: "fd-testbar", role: "status", children: [
|
|
8471
|
+
/* @__PURE__ */ jsxs61("span", { className: "fd-badge fd-badge-warning", children: [
|
|
8472
|
+
/* @__PURE__ */ jsx65("i", { className: "ph ph-flask", "aria-hidden": "true" }),
|
|
8126
8473
|
"Test mode"
|
|
8127
8474
|
] }),
|
|
8128
|
-
/* @__PURE__ */
|
|
8129
|
-
/* @__PURE__ */
|
|
8130
|
-
/* @__PURE__ */
|
|
8475
|
+
/* @__PURE__ */ jsx65("span", { className: "fd-testbar-detail", children: "Every feature is unlocked and every response is simulated \u2014 nothing here persists." }),
|
|
8476
|
+
/* @__PURE__ */ jsxs61("button", { type: "button", className: "fd-soon-link", onClick: () => onExit ? onExit() : RuntimeKit.setMode("live"), children: [
|
|
8477
|
+
/* @__PURE__ */ jsx65("i", { className: "ph ph-lightning", "aria-hidden": "true" }),
|
|
8131
8478
|
" Back to live mode"
|
|
8132
8479
|
] })
|
|
8133
8480
|
] });
|
|
8134
8481
|
}
|
|
8135
8482
|
|
|
8136
8483
|
// src/components/planner/ChannelMeta.tsx
|
|
8137
|
-
import { jsx as
|
|
8484
|
+
import { jsx as jsx66, jsxs as jsxs62 } from "react/jsx-runtime";
|
|
8138
8485
|
var CHANNEL_WEIGHTS = {
|
|
8139
8486
|
"OOH": 50,
|
|
8140
8487
|
"DOOH": 50,
|
|
@@ -8187,9 +8534,9 @@ function ChannelTag({ channel, size = "md", showLabel = true, dot = false, weigh
|
|
|
8187
8534
|
const m = ChannelMeta(channel);
|
|
8188
8535
|
const wt = channelWeightOf(channel || "");
|
|
8189
8536
|
if (dot) {
|
|
8190
|
-
return /* @__PURE__ */
|
|
8537
|
+
return /* @__PURE__ */ jsx66("span", { className: ["fd-chan-dot", className].filter(Boolean).join(" "), title: m.name, style: { background: m.color }, ...rest });
|
|
8191
8538
|
}
|
|
8192
|
-
return /* @__PURE__ */
|
|
8539
|
+
return /* @__PURE__ */ jsxs62(
|
|
8193
8540
|
"span",
|
|
8194
8541
|
{
|
|
8195
8542
|
className: ["fd-chan", size === "sm" ? "fd-chan-sm" : "", className].filter(Boolean).join(" "),
|
|
@@ -8197,16 +8544,16 @@ function ChannelTag({ channel, size = "md", showLabel = true, dot = false, weigh
|
|
|
8197
8544
|
style: { "--chan": m.color },
|
|
8198
8545
|
...rest,
|
|
8199
8546
|
children: [
|
|
8200
|
-
/* @__PURE__ */
|
|
8201
|
-
showLabel ? /* @__PURE__ */
|
|
8202
|
-
weight ? /* @__PURE__ */
|
|
8547
|
+
/* @__PURE__ */ jsx66("i", { className: "ph ph-" + m.icon, "aria-hidden": "true" }),
|
|
8548
|
+
showLabel ? /* @__PURE__ */ jsx66("span", { className: "fd-chan-label", children: m.label }) : null,
|
|
8549
|
+
weight ? /* @__PURE__ */ jsx66("span", { className: "fd-chan-weight", children: wt || 0 }) : null
|
|
8203
8550
|
]
|
|
8204
8551
|
}
|
|
8205
8552
|
);
|
|
8206
8553
|
}
|
|
8207
8554
|
|
|
8208
8555
|
// src/components/planner/SaturationDistribution.tsx
|
|
8209
|
-
import { jsx as
|
|
8556
|
+
import { jsx as jsx67, jsxs as jsxs63 } from "react/jsx-runtime";
|
|
8210
8557
|
var BANDS2 = [
|
|
8211
8558
|
{ key: "weak", label: "Weak", n: 1, range: "< 50" },
|
|
8212
8559
|
{ key: "adequate", label: "Adequate", n: 2, range: "50\u2013100" },
|
|
@@ -8217,24 +8564,24 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
|
|
|
8217
8564
|
const grouped = BANDS2.map((b) => ({ ...b, items: campuses.filter((c) => c.band === b.key) }));
|
|
8218
8565
|
const tallest = Math.max(1, ...grouped.map((g) => g.items.length));
|
|
8219
8566
|
const floor = BANDS2.find((b) => b.key === floorBand) || BANDS2[0];
|
|
8220
|
-
return /* @__PURE__ */
|
|
8221
|
-
/* @__PURE__ */
|
|
8567
|
+
return /* @__PURE__ */ jsxs63("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 16 }, children: [
|
|
8568
|
+
/* @__PURE__ */ jsx67("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,minmax(0,1fr))", gap: 12, alignItems: "end" }, children: grouped.map((g) => {
|
|
8222
8569
|
const mark = "var(--csi-" + g.n + "-mark)";
|
|
8223
8570
|
const active = selectedBand === g.key;
|
|
8224
|
-
return /* @__PURE__ */
|
|
8571
|
+
return /* @__PURE__ */ jsxs63(
|
|
8225
8572
|
"button",
|
|
8226
8573
|
{
|
|
8227
8574
|
type: "button",
|
|
8228
8575
|
onClick: onSelectBand ? () => onSelectBand(active ? null : g.key) : void 0,
|
|
8229
8576
|
style: { display: "flex", flexDirection: "column", justifyContent: "flex-end", gap: 10, padding: 12, border: "1px solid " + (active ? "var(--border-strong)" : "var(--border)"), borderRadius: "var(--r-lg)", background: active ? "var(--surface-2)" : "var(--surface)", cursor: onSelectBand ? "pointer" : "default", textAlign: "left", minHeight: 190, minWidth: 0, transition: "background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)" },
|
|
8230
8577
|
children: [
|
|
8231
|
-
/* @__PURE__ */
|
|
8578
|
+
/* @__PURE__ */ jsx67("span", { style: { display: "flex", flexDirection: "column-reverse", gap: 4, minHeight: tallest * 24, minWidth: 0 }, children: loading ? Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsx67("span", { className: "fd-skel", style: { height: 16, borderRadius: 4 } }, i)) : g.items.map((c) => (
|
|
8232
8579
|
// `overflow: hidden` + `white-space: nowrap` only clips once the span
|
|
8233
8580
|
// actually HAS a bounded width to clip against (see the grid/flex
|
|
8234
8581
|
// `minWidth: 0` chain above) — `textOverflow: ellipsis` then makes
|
|
8235
8582
|
// that clip readable instead of a hard cut mid-letter; the full name
|
|
8236
8583
|
// is still one hover away via `title`.
|
|
8237
|
-
/* @__PURE__ */
|
|
8584
|
+
/* @__PURE__ */ jsx67(
|
|
8238
8585
|
"span",
|
|
8239
8586
|
{
|
|
8240
8587
|
title: c.name + " \xB7 " + c.crp,
|
|
@@ -8244,13 +8591,13 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
|
|
|
8244
8591
|
c.name
|
|
8245
8592
|
)
|
|
8246
8593
|
)) }),
|
|
8247
|
-
/* @__PURE__ */
|
|
8248
|
-
/* @__PURE__ */
|
|
8249
|
-
/* @__PURE__ */
|
|
8594
|
+
/* @__PURE__ */ jsxs63("span", { className: "fd-row", style: { gap: 8, alignItems: "baseline" }, children: [
|
|
8595
|
+
/* @__PURE__ */ jsx67("span", { className: "fd-num-hero", style: { fontSize: 30, color: g.items.length ? "var(--text)" : "var(--text-disabled)" }, children: loading ? "\u2013" : g.items.length }),
|
|
8596
|
+
/* @__PURE__ */ jsx67("span", { className: "fd-meter", style: { gap: 2, color: mark }, "aria-hidden": "true", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx67("span", { className: "fd-meter-seg" + (i <= g.n ? " is-on" : ""), style: { width: 4, height: 9 } }, i)) })
|
|
8250
8597
|
] }),
|
|
8251
|
-
/* @__PURE__ */
|
|
8252
|
-
/* @__PURE__ */
|
|
8253
|
-
/* @__PURE__ */
|
|
8598
|
+
/* @__PURE__ */ jsxs63("span", { className: "fd-stack", style: { gap: 1 }, children: [
|
|
8599
|
+
/* @__PURE__ */ jsx67("span", { className: "fd-label-lg", children: g.label }),
|
|
8600
|
+
/* @__PURE__ */ jsxs63("span", { className: "fd-body-sm fd-muted", style: { fontSize: 11.5 }, children: [
|
|
8254
8601
|
"CRP ",
|
|
8255
8602
|
g.range
|
|
8256
8603
|
] })
|
|
@@ -8260,10 +8607,10 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
|
|
|
8260
8607
|
g.key
|
|
8261
8608
|
);
|
|
8262
8609
|
}) }),
|
|
8263
|
-
floorBand ? /* @__PURE__ */
|
|
8264
|
-
/* @__PURE__ */
|
|
8265
|
-
/* @__PURE__ */
|
|
8266
|
-
/* @__PURE__ */
|
|
8610
|
+
floorBand ? /* @__PURE__ */ jsxs63("div", { className: "fd-row", style: { gap: 10, padding: "12px 14px", borderRadius: "var(--r-md)", background: "var(--surface-2)", border: "1px solid var(--border)", flexWrap: "wrap" }, children: [
|
|
8611
|
+
/* @__PURE__ */ jsx67("span", { className: "fd-meter", style: { gap: 2, color: "var(--csi-" + floor.n + "-mark)" }, "aria-hidden": "true", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx67("span", { className: "fd-meter-seg" + (i <= floor.n ? " is-on" : ""), style: { width: 5, height: 12 } }, i)) }),
|
|
8612
|
+
/* @__PURE__ */ jsxs63("span", { className: "fd-body-sm fd-secondary", style: { flex: 1, minWidth: 240 }, children: [
|
|
8613
|
+
/* @__PURE__ */ jsxs63("strong", { style: { color: "var(--text)" }, children: [
|
|
8267
8614
|
"Plan floor ",
|
|
8268
8615
|
floorScore
|
|
8269
8616
|
] }),
|
|
@@ -8274,8 +8621,8 @@ function SaturationDistribution({ campuses = [], floorBand, floorScore, onSelect
|
|
|
8274
8621
|
}
|
|
8275
8622
|
|
|
8276
8623
|
// src/components/planner/MixGap.tsx
|
|
8277
|
-
import * as
|
|
8278
|
-
import { Fragment as
|
|
8624
|
+
import * as React42 from "react";
|
|
8625
|
+
import { Fragment as Fragment20, jsx as jsx68, jsxs as jsxs64 } from "react/jsx-runtime";
|
|
8279
8626
|
var MIX_GAP_LAYOUT = {
|
|
8280
8627
|
trackHeight: 30,
|
|
8281
8628
|
barHeight: 20,
|
|
@@ -8289,19 +8636,19 @@ function mixGapOffset(value, max) {
|
|
|
8289
8636
|
}
|
|
8290
8637
|
function MixGap({ rows = [], loading = false, className = "" }) {
|
|
8291
8638
|
const max = Math.max(1, ...rows.flatMap((r) => [r.target, r.realized]));
|
|
8292
|
-
const [hover, setHover] =
|
|
8639
|
+
const [hover, setHover] = React42.useState(null);
|
|
8293
8640
|
const toneOf = (gap) => gap >= -1 ? "ok" : gap >= -4 ? "warn" : "danger";
|
|
8294
8641
|
const TONE = { ok: "var(--ok-solid)", warn: "var(--warn-solid)", danger: "var(--danger-solid)" };
|
|
8295
8642
|
const { trackHeight, barHeight, targetWidth, labelGap } = MIX_GAP_LAYOUT;
|
|
8296
8643
|
const barTop = (trackHeight - barHeight) / 2;
|
|
8297
|
-
return /* @__PURE__ */
|
|
8298
|
-
/* @__PURE__ */
|
|
8299
|
-
[["On target", TONE.ok], ["Close", TONE.warn], ["Short", TONE.danger]].map(([l, c]) => /* @__PURE__ */
|
|
8300
|
-
/* @__PURE__ */
|
|
8644
|
+
return /* @__PURE__ */ jsxs64("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 4 }, children: [
|
|
8645
|
+
/* @__PURE__ */ jsxs64("div", { className: "fd-row", style: { gap: 16, justifyContent: "flex-end", paddingBottom: 6, flexWrap: "wrap" }, children: [
|
|
8646
|
+
[["On target", TONE.ok], ["Close", TONE.warn], ["Short", TONE.danger]].map(([l, c]) => /* @__PURE__ */ jsxs64("span", { className: "fd-row fd-body-sm fd-muted", style: { gap: 6 }, children: [
|
|
8647
|
+
/* @__PURE__ */ jsx68("span", { style: { width: 14, height: 9, borderRadius: 2, background: c } }),
|
|
8301
8648
|
l
|
|
8302
8649
|
] }, l)),
|
|
8303
|
-
/* @__PURE__ */
|
|
8304
|
-
/* @__PURE__ */
|
|
8650
|
+
/* @__PURE__ */ jsxs64("span", { className: "fd-row fd-body-sm fd-muted", style: { gap: 6 }, children: [
|
|
8651
|
+
/* @__PURE__ */ jsx68("span", { "data-mixgap-legend-target": "", style: { width: targetWidth, height: 16, borderRadius: 1, background: MIX_GAP_TARGET_STRIPE } }),
|
|
8305
8652
|
"Target"
|
|
8306
8653
|
] })
|
|
8307
8654
|
] }),
|
|
@@ -8310,7 +8657,7 @@ function MixGap({ rows = [], loading = false, className = "" }) {
|
|
|
8310
8657
|
const tone = toneOf(gap);
|
|
8311
8658
|
const tip = "Realized " + r.realized + "% vs " + r.target + "% target" + (gap < -1 ? " \xB7 " + Math.abs(gap).toFixed(0) + " pts short" : gap > 1 ? " \xB7 " + gap.toFixed(0) + " pts over" : " \xB7 on target") + (r.note ? " \u2014 " + r.note : "");
|
|
8312
8659
|
const labelLeft = r.realized > r.target ? `calc(${mixGapOffset(r.realized, max)} + ${labelGap}px)` : `calc(${mixGapOffset(r.target, max)} + ${targetWidth / 2 + labelGap}px)`;
|
|
8313
|
-
return /* @__PURE__ */
|
|
8660
|
+
return /* @__PURE__ */ jsxs64(
|
|
8314
8661
|
"div",
|
|
8315
8662
|
{
|
|
8316
8663
|
className: "fd-row",
|
|
@@ -8318,15 +8665,15 @@ function MixGap({ rows = [], loading = false, className = "" }) {
|
|
|
8318
8665
|
onMouseEnter: () => setHover(r.channel),
|
|
8319
8666
|
onMouseLeave: () => setHover(null),
|
|
8320
8667
|
children: [
|
|
8321
|
-
/* @__PURE__ */
|
|
8322
|
-
/* @__PURE__ */
|
|
8323
|
-
/* @__PURE__ */
|
|
8324
|
-
/* @__PURE__ */
|
|
8325
|
-
/* @__PURE__ */
|
|
8668
|
+
/* @__PURE__ */ jsx68("span", { style: { width: 128, flex: "none" }, children: /* @__PURE__ */ jsx68(ChannelTag, { channel: r.channel, size: "sm" }) }),
|
|
8669
|
+
/* @__PURE__ */ jsx68("span", { style: { position: "relative", flex: 1, height: trackHeight, minWidth: 120 }, children: loading ? /* @__PURE__ */ jsx68("span", { className: "fd-skel", style: { position: "absolute", left: 0, right: 0, top: barTop, height: barHeight, borderRadius: 3 } }) : /* @__PURE__ */ jsxs64(Fragment20, { children: [
|
|
8670
|
+
/* @__PURE__ */ jsx68("span", { "data-mixgap-bar": "", style: { position: "absolute", left: 0, top: barTop, height: barHeight, width: mixGapOffset(r.realized, max), background: TONE[tone], borderRadius: "2px 3px 3px 2px", transition: "width var(--dur-slow) var(--ease), background var(--dur-base) var(--ease)" } }),
|
|
8671
|
+
/* @__PURE__ */ jsx68("span", { "data-mixgap-target": "", style: { position: "absolute", left: `calc(${mixGapOffset(r.target, max)} - ${targetWidth / 2}px)`, top: 0, width: targetWidth, height: trackHeight, background: MIX_GAP_TARGET_STRIPE, borderRadius: 1 } }),
|
|
8672
|
+
/* @__PURE__ */ jsxs64("span", { "data-mixgap-label": "", className: "fd-num", style: { position: "absolute", left: labelLeft, top: "50%", transform: "translateY(-50%)", fontSize: 12, color: "var(--text-muted)", whiteSpace: "nowrap" }, children: [
|
|
8326
8673
|
r.realized,
|
|
8327
8674
|
"%"
|
|
8328
8675
|
] }),
|
|
8329
|
-
hover === r.channel ? /* @__PURE__ */
|
|
8676
|
+
hover === r.channel ? /* @__PURE__ */ jsx68("span", { className: "fd-tooltip", role: "tooltip", style: { position: "absolute", left: 0, right: "auto", bottom: trackHeight + 4, maxWidth: "min(340px, 100%)", whiteSpace: "normal", textAlign: "left", width: "max-content" }, children: tip }) : null
|
|
8330
8677
|
] }) })
|
|
8331
8678
|
]
|
|
8332
8679
|
},
|
|
@@ -8337,7 +8684,7 @@ function MixGap({ rows = [], loading = false, className = "" }) {
|
|
|
8337
8684
|
}
|
|
8338
8685
|
|
|
8339
8686
|
// src/components/planner/ChannelContribution.tsx
|
|
8340
|
-
import { jsx as
|
|
8687
|
+
import { jsx as jsx69, jsxs as jsxs65 } from "react/jsx-runtime";
|
|
8341
8688
|
var money = (n) => "$" + Math.round(n).toLocaleString();
|
|
8342
8689
|
function ChannelContribution({
|
|
8343
8690
|
channels = [],
|
|
@@ -8353,9 +8700,9 @@ function ChannelContribution({
|
|
|
8353
8700
|
const grand = total !== void 0 ? total : base + bonus;
|
|
8354
8701
|
const pct = (v) => grand ? v / grand * 100 : 0;
|
|
8355
8702
|
const spendTotal = channels.reduce((s, c) => s + Number(String(c.spend || 0).replace(/[^0-9.]/g, "")), 0);
|
|
8356
|
-
return /* @__PURE__ */
|
|
8357
|
-
loading ? /* @__PURE__ */
|
|
8358
|
-
channels.map((c) => /* @__PURE__ */
|
|
8703
|
+
return /* @__PURE__ */ jsxs65("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 16 }, children: [
|
|
8704
|
+
loading ? /* @__PURE__ */ jsx69("span", { className: "fd-skel", style: { height: 34, borderRadius: "var(--r-sm)" } }) : /* @__PURE__ */ jsxs65("div", { className: "fd-row", style: { height: 34, borderRadius: "var(--r-sm)", overflow: "hidden", gap: 2, background: "var(--surface-3)" }, children: [
|
|
8705
|
+
channels.map((c) => /* @__PURE__ */ jsx69(
|
|
8359
8706
|
"span",
|
|
8360
8707
|
{
|
|
8361
8708
|
title: c.name + " \xB7 " + c.crp.toFixed(1) + " CRP",
|
|
@@ -8364,7 +8711,7 @@ function ChannelContribution({
|
|
|
8364
8711
|
},
|
|
8365
8712
|
c.name
|
|
8366
8713
|
)),
|
|
8367
|
-
bonus > 0 ? /* @__PURE__ */
|
|
8714
|
+
bonus > 0 ? /* @__PURE__ */ jsx69(
|
|
8368
8715
|
"span",
|
|
8369
8716
|
{
|
|
8370
8717
|
title: "Surround-sound bonus +" + bonusPct + "%",
|
|
@@ -8373,18 +8720,18 @@ function ChannelContribution({
|
|
|
8373
8720
|
}
|
|
8374
8721
|
) : null
|
|
8375
8722
|
] }),
|
|
8376
|
-
showTable ? /* @__PURE__ */
|
|
8377
|
-
/* @__PURE__ */
|
|
8378
|
-
/* @__PURE__ */
|
|
8379
|
-
/* @__PURE__ */
|
|
8380
|
-
/* @__PURE__ */
|
|
8381
|
-
/* @__PURE__ */
|
|
8382
|
-
/* @__PURE__ */
|
|
8723
|
+
showTable ? /* @__PURE__ */ jsxs65("table", { className: "fd-table", style: { fontSize: "var(--body-sm-size)" }, children: [
|
|
8724
|
+
/* @__PURE__ */ jsx69("thead", { children: /* @__PURE__ */ jsxs65("tr", { children: [
|
|
8725
|
+
/* @__PURE__ */ jsx69("th", { style: { width: "34%" }, children: "Channel" }),
|
|
8726
|
+
/* @__PURE__ */ jsx69("th", { style: { width: "16%" }, children: "Weight" }),
|
|
8727
|
+
/* @__PURE__ */ jsx69("th", { className: "is-num", style: { width: "16%" }, children: "Spend" }),
|
|
8728
|
+
/* @__PURE__ */ jsx69("th", { className: "is-num", style: { width: "17%" }, children: "CRP" }),
|
|
8729
|
+
/* @__PURE__ */ jsx69("th", { className: "is-num", style: { width: "17%" }, children: "Share" })
|
|
8383
8730
|
] }) }),
|
|
8384
|
-
/* @__PURE__ */
|
|
8385
|
-
channels.map((c) => /* @__PURE__ */
|
|
8386
|
-
/* @__PURE__ */
|
|
8387
|
-
/* @__PURE__ */
|
|
8731
|
+
/* @__PURE__ */ jsxs65("tbody", { children: [
|
|
8732
|
+
channels.map((c) => /* @__PURE__ */ jsxs65("tr", { children: [
|
|
8733
|
+
/* @__PURE__ */ jsx69("td", { children: /* @__PURE__ */ jsx69(ChannelTag, { channel: c.name, size: "sm" }) }),
|
|
8734
|
+
/* @__PURE__ */ jsx69("td", { children: /* @__PURE__ */ jsx69(
|
|
8388
8735
|
"span",
|
|
8389
8736
|
{
|
|
8390
8737
|
className: "fd-badge fd-badge-neutral",
|
|
@@ -8393,38 +8740,38 @@ function ChannelContribution({
|
|
|
8393
8740
|
children: "weight " + (channelWeightOf(c.name) || 0)
|
|
8394
8741
|
}
|
|
8395
8742
|
) }),
|
|
8396
|
-
/* @__PURE__ */
|
|
8397
|
-
/* @__PURE__ */
|
|
8398
|
-
/* @__PURE__ */
|
|
8743
|
+
/* @__PURE__ */ jsx69("td", { className: "is-num", children: c.spend }),
|
|
8744
|
+
/* @__PURE__ */ jsx69("td", { className: "is-num", children: c.crp.toFixed(1) }),
|
|
8745
|
+
/* @__PURE__ */ jsxs65("td", { className: "is-num", children: [
|
|
8399
8746
|
pct(c.crp).toFixed(0),
|
|
8400
8747
|
"%"
|
|
8401
8748
|
] })
|
|
8402
8749
|
] }, c.name)),
|
|
8403
|
-
bonus > 0 ? /* @__PURE__ */
|
|
8404
|
-
/* @__PURE__ */
|
|
8405
|
-
/* @__PURE__ */
|
|
8750
|
+
bonus > 0 ? /* @__PURE__ */ jsxs65("tr", { children: [
|
|
8751
|
+
/* @__PURE__ */ jsx69("td", { children: /* @__PURE__ */ jsxs65("span", { className: "fd-row", style: { gap: 9, fontWeight: 600 }, children: [
|
|
8752
|
+
/* @__PURE__ */ jsx69("span", { style: { width: 9, height: 9, borderRadius: 2, background: "repeating-linear-gradient(135deg,var(--csi-3-mark) 0 3px,var(--csi-2-mark) 3px 6px)", flex: "none" } }),
|
|
8406
8753
|
"Surround-sound bonus"
|
|
8407
8754
|
] }) }),
|
|
8408
|
-
/* @__PURE__ */
|
|
8755
|
+
/* @__PURE__ */ jsx69("td", { children: /* @__PURE__ */ jsxs65("span", { className: "fd-badge fd-badge-success", style: { height: 20, fontSize: 11 }, children: [
|
|
8409
8756
|
"+",
|
|
8410
8757
|
bonusPct,
|
|
8411
8758
|
"% of +",
|
|
8412
8759
|
bonusMax,
|
|
8413
8760
|
"%"
|
|
8414
8761
|
] }) }),
|
|
8415
|
-
/* @__PURE__ */
|
|
8416
|
-
/* @__PURE__ */
|
|
8417
|
-
/* @__PURE__ */
|
|
8762
|
+
/* @__PURE__ */ jsx69("td", { className: "is-num fd-muted", children: "\u2014" }),
|
|
8763
|
+
/* @__PURE__ */ jsx69("td", { className: "is-num", children: bonus.toFixed(1) }),
|
|
8764
|
+
/* @__PURE__ */ jsxs65("td", { className: "is-num", children: [
|
|
8418
8765
|
pct(bonus).toFixed(0),
|
|
8419
8766
|
"%"
|
|
8420
8767
|
] })
|
|
8421
8768
|
] }) : null,
|
|
8422
|
-
/* @__PURE__ */
|
|
8423
|
-
/* @__PURE__ */
|
|
8424
|
-
/* @__PURE__ */
|
|
8425
|
-
/* @__PURE__ */
|
|
8426
|
-
/* @__PURE__ */
|
|
8427
|
-
/* @__PURE__ */
|
|
8769
|
+
/* @__PURE__ */ jsxs65("tr", { style: { background: "var(--surface-2)" }, children: [
|
|
8770
|
+
/* @__PURE__ */ jsx69("td", { style: { fontWeight: 700 }, children: "Campus total" }),
|
|
8771
|
+
/* @__PURE__ */ jsx69("td", {}),
|
|
8772
|
+
/* @__PURE__ */ jsx69("td", { className: "is-num", style: { fontWeight: 700 }, children: money(spendTotal) }),
|
|
8773
|
+
/* @__PURE__ */ jsx69("td", { className: "is-num", style: { fontWeight: 700 }, children: grand.toFixed(1) }),
|
|
8774
|
+
/* @__PURE__ */ jsx69("td", { className: "is-num", style: { fontWeight: 700 }, children: "100%" })
|
|
8428
8775
|
] })
|
|
8429
8776
|
] })
|
|
8430
8777
|
] }) : null
|
|
@@ -8432,8 +8779,8 @@ function ChannelContribution({
|
|
|
8432
8779
|
}
|
|
8433
8780
|
|
|
8434
8781
|
// src/components/planner/BudgetReallocator.tsx
|
|
8435
|
-
import * as
|
|
8436
|
-
import { Fragment as
|
|
8782
|
+
import * as React43 from "react";
|
|
8783
|
+
import { Fragment as Fragment21, jsx as jsx70, jsxs as jsxs66 } from "react/jsx-runtime";
|
|
8437
8784
|
var bandFor = (crp) => crp >= 200 ? "dominant" : crp >= 100 ? "strong" : crp >= 50 ? "adequate" : "weak";
|
|
8438
8785
|
function BudgetReallocator({
|
|
8439
8786
|
campus,
|
|
@@ -8448,8 +8795,8 @@ function BudgetReallocator({
|
|
|
8448
8795
|
onCancel,
|
|
8449
8796
|
className = ""
|
|
8450
8797
|
}) {
|
|
8451
|
-
const [draft, setDraft] =
|
|
8452
|
-
|
|
8798
|
+
const [draft, setDraft] = React43.useState(spend);
|
|
8799
|
+
React43.useEffect(() => setDraft(spend), [spend]);
|
|
8453
8800
|
const dirty = draft !== spend;
|
|
8454
8801
|
const nextCrp = scoreFor ? scoreFor(draft) : crp;
|
|
8455
8802
|
const nextBand = bandFor(nextCrp);
|
|
@@ -8464,10 +8811,10 @@ function BudgetReallocator({
|
|
|
8464
8811
|
setDraft(spend);
|
|
8465
8812
|
if (onCancel) onCancel();
|
|
8466
8813
|
};
|
|
8467
|
-
const trackRef =
|
|
8468
|
-
const chipRef =
|
|
8469
|
-
const [chipLeft, setChipLeft] =
|
|
8470
|
-
|
|
8814
|
+
const trackRef = React43.useRef(null);
|
|
8815
|
+
const chipRef = React43.useRef(null);
|
|
8816
|
+
const [chipLeft, setChipLeft] = React43.useState(null);
|
|
8817
|
+
React43.useLayoutEffect(() => {
|
|
8471
8818
|
const track = trackRef.current;
|
|
8472
8819
|
if (!dirty || !track) {
|
|
8473
8820
|
setChipLeft(null);
|
|
@@ -8488,23 +8835,23 @@ function BudgetReallocator({
|
|
|
8488
8835
|
ro.observe(track);
|
|
8489
8836
|
return () => ro.disconnect();
|
|
8490
8837
|
}, [dirty, pct]);
|
|
8491
|
-
return /* @__PURE__ */
|
|
8838
|
+
return /* @__PURE__ */ jsxs66(
|
|
8492
8839
|
"div",
|
|
8493
8840
|
{
|
|
8494
8841
|
className: ["fd-stack", className].filter(Boolean).join(" "),
|
|
8495
8842
|
style: { gap: 14, padding: 18, border: "1px solid " + (dirty ? "var(--border-brand)" : "var(--border)"), borderRadius: "var(--r-lg)", background: dirty ? "var(--surface-brand)" : "var(--surface)", color: "var(--text)", transition: "background var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease)" },
|
|
8496
8843
|
children: [
|
|
8497
|
-
/* @__PURE__ */
|
|
8498
|
-
/* @__PURE__ */
|
|
8499
|
-
/* @__PURE__ */
|
|
8500
|
-
dirty ? /* @__PURE__ */
|
|
8501
|
-
/* @__PURE__ */
|
|
8502
|
-
/* @__PURE__ */
|
|
8844
|
+
/* @__PURE__ */ jsxs66("div", { className: "fd-row", style: { gap: 12, flexWrap: "wrap" }, children: [
|
|
8845
|
+
/* @__PURE__ */ jsx70("span", { className: "fd-h4", style: { flex: 1, minWidth: 140 }, children: campus }),
|
|
8846
|
+
/* @__PURE__ */ jsx70(CsiBadge, { band: nowBand, crp, size: "medium" }),
|
|
8847
|
+
dirty ? /* @__PURE__ */ jsxs66(Fragment21, { children: [
|
|
8848
|
+
/* @__PURE__ */ jsx70("i", { className: "ph ph-arrow-right fd-muted", "aria-hidden": "true" }),
|
|
8849
|
+
/* @__PURE__ */ jsx70(CsiBadge, { band: nextBand, crp: nextCrp, size: "medium", preview: true })
|
|
8503
8850
|
] }) : null
|
|
8504
8851
|
] }),
|
|
8505
|
-
/* @__PURE__ */
|
|
8506
|
-
/* @__PURE__ */
|
|
8507
|
-
dirty ? /* @__PURE__ */
|
|
8852
|
+
/* @__PURE__ */ jsxs66("div", { className: "fd-slider", ref: trackRef, children: [
|
|
8853
|
+
/* @__PURE__ */ jsx70("span", { className: "fd-slider-fill", style: { width: pct + "%" } }),
|
|
8854
|
+
dirty ? /* @__PURE__ */ jsx70(
|
|
8508
8855
|
"span",
|
|
8509
8856
|
{
|
|
8510
8857
|
ref: chipRef,
|
|
@@ -8516,7 +8863,7 @@ function BudgetReallocator({
|
|
|
8516
8863
|
children: (delta < 0 ? "\u2212$" : "+$") + Math.abs(delta).toLocaleString()
|
|
8517
8864
|
}
|
|
8518
8865
|
) : null,
|
|
8519
|
-
/* @__PURE__ */
|
|
8866
|
+
/* @__PURE__ */ jsx70(
|
|
8520
8867
|
"input",
|
|
8521
8868
|
{
|
|
8522
8869
|
type: "range",
|
|
@@ -8532,13 +8879,13 @@ function BudgetReallocator({
|
|
|
8532
8879
|
}
|
|
8533
8880
|
)
|
|
8534
8881
|
] }),
|
|
8535
|
-
/* @__PURE__ */
|
|
8536
|
-
/* @__PURE__ */
|
|
8537
|
-
/* @__PURE__ */
|
|
8538
|
-
/* @__PURE__ */
|
|
8882
|
+
/* @__PURE__ */ jsxs66("div", { className: "fd-row", style: { gap: 12, flexWrap: "wrap" }, children: [
|
|
8883
|
+
/* @__PURE__ */ jsxs66("span", { className: "fd-stack", style: { gap: 2, flex: 1, minWidth: 150 }, children: [
|
|
8884
|
+
/* @__PURE__ */ jsx70("span", { className: "fd-num", style: { fontSize: 19, fontWeight: 700 }, children: "$" + draft.toLocaleString() }),
|
|
8885
|
+
/* @__PURE__ */ jsx70("span", { className: "fd-body-sm fd-muted", children: "Arrow keys step $100, shift+arrow $1,000. Escape reverts." })
|
|
8539
8886
|
] }),
|
|
8540
|
-
/* @__PURE__ */
|
|
8541
|
-
/* @__PURE__ */
|
|
8887
|
+
/* @__PURE__ */ jsx70("button", { type: "button", className: "fd-btn fd-btn-ghost", disabled: !dirty, onClick: revert, children: "Cancel" }),
|
|
8888
|
+
/* @__PURE__ */ jsx70(
|
|
8542
8889
|
"button",
|
|
8543
8890
|
{
|
|
8544
8891
|
type: "button",
|
|
@@ -8557,7 +8904,7 @@ function BudgetReallocator({
|
|
|
8557
8904
|
}
|
|
8558
8905
|
|
|
8559
8906
|
// src/components/planner/SurroundSound.tsx
|
|
8560
|
-
import { jsx as
|
|
8907
|
+
import { jsx as jsx71, jsxs as jsxs67 } from "react/jsx-runtime";
|
|
8561
8908
|
var CATEGORIES = [
|
|
8562
8909
|
{ key: "ooh", label: "OOH", icon: "flag-banner", color: "var(--ch-ooh)" },
|
|
8563
8910
|
{ key: "transit", label: "Transit", icon: "bus", color: "var(--ch-transit)" },
|
|
@@ -8569,26 +8916,26 @@ var CATEGORIES = [
|
|
|
8569
8916
|
function SurroundSound({ present = [], bonusPct = 0, bonusMax = 40, missedCost, className = "" }) {
|
|
8570
8917
|
const earned = Math.max(0, Math.min(1, bonusPct / bonusMax));
|
|
8571
8918
|
const single = present.length <= 2;
|
|
8572
|
-
return /* @__PURE__ */
|
|
8573
|
-
/* @__PURE__ */
|
|
8919
|
+
return /* @__PURE__ */ jsxs67("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 14 }, children: [
|
|
8920
|
+
/* @__PURE__ */ jsx71("div", { className: "fd-row", style: { gap: 8, flexWrap: "wrap" }, children: CATEGORIES.map((c) => {
|
|
8574
8921
|
const on = present.includes(c.key);
|
|
8575
|
-
return /* @__PURE__ */
|
|
8922
|
+
return /* @__PURE__ */ jsxs67(
|
|
8576
8923
|
"span",
|
|
8577
8924
|
{
|
|
8578
8925
|
title: c.label + (on ? " \u2014 present" : " \u2014 not bought"),
|
|
8579
8926
|
style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 5, width: 62, padding: "10px 0", borderRadius: "var(--r-md)", border: "1px solid " + (on ? "var(--csi-2-edge)" : "var(--border)"), background: on ? "var(--csi-2-fill)" : "var(--surface-2)", color: on ? "var(--csi-2-text)" : "var(--text-disabled)" },
|
|
8580
8927
|
children: [
|
|
8581
|
-
/* @__PURE__ */
|
|
8582
|
-
/* @__PURE__ */
|
|
8928
|
+
/* @__PURE__ */ jsx71("i", { className: "ph ph-" + c.icon, style: { fontSize: 19, color: on ? c.color : "var(--text-muted)" }, "aria-hidden": "true" }),
|
|
8929
|
+
/* @__PURE__ */ jsx71("span", { style: { fontSize: 10.5, fontWeight: 700 }, children: c.label })
|
|
8583
8930
|
]
|
|
8584
8931
|
},
|
|
8585
8932
|
c.key
|
|
8586
8933
|
);
|
|
8587
8934
|
}) }),
|
|
8588
|
-
/* @__PURE__ */
|
|
8589
|
-
/* @__PURE__ */
|
|
8590
|
-
/* @__PURE__ */
|
|
8591
|
-
/* @__PURE__ */
|
|
8935
|
+
/* @__PURE__ */ jsxs67("div", { className: "fd-stack", style: { gap: 8 }, children: [
|
|
8936
|
+
/* @__PURE__ */ jsxs67("div", { className: "fd-row", style: { justifyContent: "space-between", gap: 12 }, children: [
|
|
8937
|
+
/* @__PURE__ */ jsx71("span", { className: "fd-label-lg", children: "Surround-sound bonus earned" }),
|
|
8938
|
+
/* @__PURE__ */ jsxs67("span", { className: "fd-num", style: { color: single ? "var(--warn-text)" : "var(--csi-3-mark)", fontWeight: 700 }, children: [
|
|
8592
8939
|
"+",
|
|
8593
8940
|
bonusPct,
|
|
8594
8941
|
"% of +",
|
|
@@ -8596,8 +8943,8 @@ function SurroundSound({ present = [], bonusPct = 0, bonusMax = 40, missedCost,
|
|
|
8596
8943
|
"%"
|
|
8597
8944
|
] })
|
|
8598
8945
|
] }),
|
|
8599
|
-
/* @__PURE__ */
|
|
8600
|
-
/* @__PURE__ */
|
|
8946
|
+
/* @__PURE__ */ jsx71("div", { style: { height: 8, borderRadius: "var(--r-xs)", background: "var(--surface-3)", overflow: "hidden" }, children: /* @__PURE__ */ jsx71("div", { style: { height: "100%", width: earned * 100 + "%", background: "var(--csi-3-mark)", borderRadius: "inherit", transition: "width var(--dur-slow) var(--ease)" } }) }),
|
|
8947
|
+
/* @__PURE__ */ jsxs67("span", { className: "fd-body-sm fd-secondary", style: { textWrap: "pretty" }, children: [
|
|
8601
8948
|
present.length,
|
|
8602
8949
|
" of 6 channel categories present.",
|
|
8603
8950
|
" ",
|
|
@@ -8609,7 +8956,7 @@ function SurroundSound({ present = [], bonusPct = 0, bonusMax = 40, missedCost,
|
|
|
8609
8956
|
}
|
|
8610
8957
|
|
|
8611
8958
|
// src/components/planner/Importance.tsx
|
|
8612
|
-
import { jsx as
|
|
8959
|
+
import { jsx as jsx72, jsxs as jsxs68 } from "react/jsx-runtime";
|
|
8613
8960
|
var IMPORTANCE_LEVELS = [
|
|
8614
8961
|
{ id: "nudge", rank: 1, label: "Nudge", icon: "feather", description: "Breaks a tie, nothing more." },
|
|
8615
8962
|
{ id: "lean", rank: 2, label: "Lean", icon: "arrow-bend-up-right", description: "A mild preference between otherwise equal options." },
|
|
@@ -8639,23 +8986,23 @@ function importanceTokens(id) {
|
|
|
8639
8986
|
function ImportanceTag({ level, showLabel = true, size = "sm", className = "" }) {
|
|
8640
8987
|
const lvl = importanceLevel(level);
|
|
8641
8988
|
const t = importanceTokens(level);
|
|
8642
|
-
return /* @__PURE__ */
|
|
8989
|
+
return /* @__PURE__ */ jsxs68(
|
|
8643
8990
|
"span",
|
|
8644
8991
|
{
|
|
8645
8992
|
className: ["fd-imp-tag", size === "md" ? "is-md" : "", className].filter(Boolean).join(" "),
|
|
8646
8993
|
style: { background: t.fill, borderColor: t.edge, color: t.text },
|
|
8647
8994
|
title: lvl.label + " \u2014 " + lvl.description,
|
|
8648
8995
|
children: [
|
|
8649
|
-
/* @__PURE__ */
|
|
8650
|
-
showLabel ? lvl.label : /* @__PURE__ */
|
|
8996
|
+
/* @__PURE__ */ jsx72("i", { className: "ph ph-" + lvl.icon, "aria-hidden": "true" }),
|
|
8997
|
+
showLabel ? lvl.label : /* @__PURE__ */ jsx72("span", { className: "fd-sr", children: lvl.label })
|
|
8651
8998
|
]
|
|
8652
8999
|
}
|
|
8653
9000
|
);
|
|
8654
9001
|
}
|
|
8655
9002
|
|
|
8656
9003
|
// src/components/planner/PreferenceMeter.tsx
|
|
8657
|
-
import * as
|
|
8658
|
-
import { jsx as
|
|
9004
|
+
import * as React44 from "react";
|
|
9005
|
+
import { jsx as jsx73, jsxs as jsxs69 } from "react/jsx-runtime";
|
|
8659
9006
|
function preferenceScore(criteria) {
|
|
8660
9007
|
const earned = criteria.reduce((n, c) => n + (Number(c.earned) || 0), 0);
|
|
8661
9008
|
const weight = criteria.reduce((n, c) => n + (Number(c.weight) || 0), 0);
|
|
@@ -8684,8 +9031,8 @@ function PreferenceMeter({
|
|
|
8684
9031
|
className = ""
|
|
8685
9032
|
}) {
|
|
8686
9033
|
const value = score == null ? preferenceScore(criteria) : score;
|
|
8687
|
-
const segments =
|
|
8688
|
-
return /* @__PURE__ */
|
|
9034
|
+
const segments = React44.useMemo(() => preferenceSegments(criteria), [criteria]);
|
|
9035
|
+
return /* @__PURE__ */ jsx73(
|
|
8689
9036
|
ScoreMeter,
|
|
8690
9037
|
{
|
|
8691
9038
|
score: value,
|
|
@@ -8697,22 +9044,22 @@ function PreferenceMeter({
|
|
|
8697
9044
|
placement,
|
|
8698
9045
|
className,
|
|
8699
9046
|
breakdownLabel: breakdownTitle,
|
|
8700
|
-
breakdown: /* @__PURE__ */
|
|
8701
|
-
/* @__PURE__ */
|
|
8702
|
-
/* @__PURE__ */
|
|
8703
|
-
/* @__PURE__ */
|
|
9047
|
+
breakdown: /* @__PURE__ */ jsxs69("div", { className: "fd-prefbreak", children: [
|
|
9048
|
+
/* @__PURE__ */ jsxs69("div", { className: "fd-prefbreak-head", children: [
|
|
9049
|
+
/* @__PURE__ */ jsx73("span", { className: "fd-label-lg", children: breakdownTitle }),
|
|
9050
|
+
/* @__PURE__ */ jsx73("span", { className: "fd-prefbreak-score fd-tabular", children: Math.round(value) })
|
|
8704
9051
|
] }),
|
|
8705
|
-
/* @__PURE__ */
|
|
9052
|
+
/* @__PURE__ */ jsx73("ul", { className: "fd-prefbreak-list", children: criteria.map((c) => {
|
|
8706
9053
|
const pct = c.weight > 0 ? Math.round((Number(c.earned) || 0) / c.weight * 100) : 0;
|
|
8707
|
-
return /* @__PURE__ */
|
|
8708
|
-
/* @__PURE__ */
|
|
8709
|
-
/* @__PURE__ */
|
|
8710
|
-
/* @__PURE__ */
|
|
8711
|
-
c.note ? /* @__PURE__ */
|
|
9054
|
+
return /* @__PURE__ */ jsxs69("li", { className: pct === 0 ? "fd-prefbreak-item is-unmet" : "fd-prefbreak-item", children: [
|
|
9055
|
+
/* @__PURE__ */ jsx73(ImportanceTag, { level: c.importance }),
|
|
9056
|
+
/* @__PURE__ */ jsxs69("span", { className: "fd-prefbreak-text", children: [
|
|
9057
|
+
/* @__PURE__ */ jsx73("span", { className: "fd-prefbreak-label", children: c.label }),
|
|
9058
|
+
c.note ? /* @__PURE__ */ jsx73("span", { className: "fd-prefbreak-note", children: c.note }) : null
|
|
8712
9059
|
] }),
|
|
8713
|
-
/* @__PURE__ */
|
|
9060
|
+
/* @__PURE__ */ jsxs69("span", { className: "fd-prefbreak-val fd-tabular", children: [
|
|
8714
9061
|
Math.round(Number(c.earned) || 0),
|
|
8715
|
-
/* @__PURE__ */
|
|
9062
|
+
/* @__PURE__ */ jsx73("span", { className: "fd-prefbreak-sep", children: "/" }),
|
|
8716
9063
|
Math.round(c.weight)
|
|
8717
9064
|
] })
|
|
8718
9065
|
] }, c.id);
|
|
@@ -8723,10 +9070,10 @@ function PreferenceMeter({
|
|
|
8723
9070
|
}
|
|
8724
9071
|
|
|
8725
9072
|
// src/components/chat/AgentChatPanel.tsx
|
|
8726
|
-
import * as
|
|
9073
|
+
import * as React50 from "react";
|
|
8727
9074
|
|
|
8728
9075
|
// src/components/chat/chatEngine.ts
|
|
8729
|
-
import * as
|
|
9076
|
+
import * as React45 from "react";
|
|
8730
9077
|
var CHAT_UNAVAILABLE = "chat_unavailable";
|
|
8731
9078
|
var JOB_PENDING = ["queued", "running"];
|
|
8732
9079
|
var JOB_SUCCESS = ["completed", "recovered"];
|
|
@@ -8780,22 +9127,22 @@ function useChatEngine(opts) {
|
|
|
8780
9127
|
onClear,
|
|
8781
9128
|
onFeedback
|
|
8782
9129
|
} = opts || {};
|
|
8783
|
-
const [status, setStatus] =
|
|
8784
|
-
const [threadId, setThreadId] =
|
|
8785
|
-
const [messages, setMessages] =
|
|
8786
|
-
const [queue, setQueue] =
|
|
8787
|
-
const [fatal, setFatal] =
|
|
8788
|
-
const [busy, setBusy] =
|
|
8789
|
-
const [turnStartedAt, setTurnStartedAt] =
|
|
8790
|
-
const listRef =
|
|
8791
|
-
const queueRef =
|
|
8792
|
-
const busyRef =
|
|
8793
|
-
const stoppedRef =
|
|
8794
|
-
const abortRef =
|
|
8795
|
-
const serverCount =
|
|
8796
|
-
const threadRef =
|
|
8797
|
-
const mounted =
|
|
8798
|
-
|
|
9130
|
+
const [status, setStatus] = React45.useState("idle");
|
|
9131
|
+
const [threadId, setThreadId] = React45.useState(null);
|
|
9132
|
+
const [messages, setMessages] = React45.useState([]);
|
|
9133
|
+
const [queue, setQueue] = React45.useState([]);
|
|
9134
|
+
const [fatal, setFatal] = React45.useState(null);
|
|
9135
|
+
const [busy, setBusy] = React45.useState(false);
|
|
9136
|
+
const [turnStartedAt, setTurnStartedAt] = React45.useState(null);
|
|
9137
|
+
const listRef = React45.useRef([]);
|
|
9138
|
+
const queueRef = React45.useRef([]);
|
|
9139
|
+
const busyRef = React45.useRef(false);
|
|
9140
|
+
const stoppedRef = React45.useRef(false);
|
|
9141
|
+
const abortRef = React45.useRef(null);
|
|
9142
|
+
const serverCount = React45.useRef(0);
|
|
9143
|
+
const threadRef = React45.useRef(null);
|
|
9144
|
+
const mounted = React45.useRef(true);
|
|
9145
|
+
React45.useEffect(() => {
|
|
8799
9146
|
mounted.current = true;
|
|
8800
9147
|
return () => {
|
|
8801
9148
|
mounted.current = false;
|
|
@@ -8817,14 +9164,14 @@ function useChatEngine(opts) {
|
|
|
8817
9164
|
}
|
|
8818
9165
|
return false;
|
|
8819
9166
|
};
|
|
8820
|
-
const loadThread =
|
|
9167
|
+
const loadThread = React45.useCallback(async (id) => {
|
|
8821
9168
|
const data = await apiAdapter.getThread(id);
|
|
8822
9169
|
const list = [...data && data.messages || []];
|
|
8823
9170
|
serverCount.current = list.length;
|
|
8824
9171
|
commit(list);
|
|
8825
9172
|
return list;
|
|
8826
9173
|
}, [apiAdapter]);
|
|
8827
|
-
|
|
9174
|
+
React45.useEffect(() => {
|
|
8828
9175
|
if (!apiAdapter) {
|
|
8829
9176
|
setStatus("idle");
|
|
8830
9177
|
setFatal(null);
|
|
@@ -9037,7 +9384,7 @@ function useChatEngine(opts) {
|
|
|
9037
9384
|
await dispatchTurn(turn);
|
|
9038
9385
|
}
|
|
9039
9386
|
}
|
|
9040
|
-
const send =
|
|
9387
|
+
const send = React45.useCallback((text, attachments) => {
|
|
9041
9388
|
const body = (text || "").trim();
|
|
9042
9389
|
if (!body && !(attachments && attachments.length)) return;
|
|
9043
9390
|
if (status === "disconnected") return;
|
|
@@ -9047,7 +9394,7 @@ function useChatEngine(opts) {
|
|
|
9047
9394
|
stoppedRef.current = false;
|
|
9048
9395
|
drain();
|
|
9049
9396
|
}, [status]);
|
|
9050
|
-
const stop =
|
|
9397
|
+
const stop = React45.useCallback(() => {
|
|
9051
9398
|
stoppedRef.current = true;
|
|
9052
9399
|
const ac = abortRef.current;
|
|
9053
9400
|
if (ac) {
|
|
@@ -9066,11 +9413,11 @@ function useChatEngine(opts) {
|
|
|
9066
9413
|
store.del(STORAGE_PREFIX + threadRef.current);
|
|
9067
9414
|
}
|
|
9068
9415
|
}, [apiAdapter]);
|
|
9069
|
-
const removeQueued =
|
|
9416
|
+
const removeQueued = React45.useCallback((id) => {
|
|
9070
9417
|
queueRef.current = queueRef.current.filter((t) => t.id !== id);
|
|
9071
9418
|
setQueue(queueRef.current.slice());
|
|
9072
9419
|
}, []);
|
|
9073
|
-
const retry =
|
|
9420
|
+
const retry = React45.useCallback(() => {
|
|
9074
9421
|
const list = listRef.current;
|
|
9075
9422
|
let at = -1;
|
|
9076
9423
|
for (let i = list.length - 1; i >= 0; i--) if (list[i].role === "user") {
|
|
@@ -9086,19 +9433,19 @@ function useChatEngine(opts) {
|
|
|
9086
9433
|
setQueue(queueRef.current.slice());
|
|
9087
9434
|
drain();
|
|
9088
9435
|
}, []);
|
|
9089
|
-
const clear =
|
|
9436
|
+
const clear = React45.useCallback(() => {
|
|
9090
9437
|
commit([]);
|
|
9091
9438
|
serverCount.current = 0;
|
|
9092
9439
|
queueRef.current = [];
|
|
9093
9440
|
setQueue([]);
|
|
9094
9441
|
onClear && onClear();
|
|
9095
9442
|
}, [onClear]);
|
|
9096
|
-
const setFeedback =
|
|
9443
|
+
const setFeedback = React45.useCallback((id, value) => {
|
|
9097
9444
|
patch(id, (m) => ({ feedback: m.feedback === value ? null : value }));
|
|
9098
9445
|
const msg = listRef.current.find((m) => m.id === id);
|
|
9099
9446
|
onFeedback && onFeedback({ message: msg, feedback: msg ? msg.feedback : value });
|
|
9100
9447
|
}, [onFeedback]);
|
|
9101
|
-
const reload =
|
|
9448
|
+
const reload = React45.useCallback(async () => {
|
|
9102
9449
|
if (!threadRef.current) return;
|
|
9103
9450
|
setStatus("loading");
|
|
9104
9451
|
try {
|
|
@@ -9136,11 +9483,11 @@ var ChatKit = {
|
|
|
9136
9483
|
};
|
|
9137
9484
|
|
|
9138
9485
|
// src/components/chat/ChatTranscript.tsx
|
|
9139
|
-
import * as
|
|
9486
|
+
import * as React47 from "react";
|
|
9140
9487
|
|
|
9141
9488
|
// src/components/chat/ChatTurn.tsx
|
|
9142
|
-
import * as
|
|
9143
|
-
import { jsx as
|
|
9489
|
+
import * as React46 from "react";
|
|
9490
|
+
import { jsx as jsx74, jsxs as jsxs70 } from "react/jsx-runtime";
|
|
9144
9491
|
function JsonView({ value }) {
|
|
9145
9492
|
let text;
|
|
9146
9493
|
try {
|
|
@@ -9148,67 +9495,67 @@ function JsonView({ value }) {
|
|
|
9148
9495
|
} catch (e) {
|
|
9149
9496
|
text = String(value);
|
|
9150
9497
|
}
|
|
9151
|
-
return /* @__PURE__ */
|
|
9498
|
+
return /* @__PURE__ */ jsx74(CodeBlock, { code: text, language: "json", collapseAfter: 18 });
|
|
9152
9499
|
}
|
|
9153
9500
|
function PacketCard({ packet, schema, render, onApply, applied }) {
|
|
9154
9501
|
if (!packet) return null;
|
|
9155
9502
|
const s = schema || {};
|
|
9156
9503
|
const invalid = packet.valid === false;
|
|
9157
9504
|
const title = s.heading || packet.type;
|
|
9158
|
-
return /* @__PURE__ */
|
|
9159
|
-
/* @__PURE__ */
|
|
9160
|
-
/* @__PURE__ */
|
|
9161
|
-
/* @__PURE__ */
|
|
9162
|
-
packet.repaired ? /* @__PURE__ */
|
|
9163
|
-
invalid ? /* @__PURE__ */
|
|
9505
|
+
return /* @__PURE__ */ jsxs70("section", { className: "fdc-packet" + (invalid ? " is-invalid" : ""), "aria-label": title, children: [
|
|
9506
|
+
/* @__PURE__ */ jsxs70("header", { className: "fdc-packet-head", children: [
|
|
9507
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-" + (s.icon || "package"), "aria-hidden": "true" }),
|
|
9508
|
+
/* @__PURE__ */ jsx74("span", { className: "fdc-packet-title", title: truncationTitle(title), children: title }),
|
|
9509
|
+
packet.repaired ? /* @__PURE__ */ jsx74("span", { className: "fdc-packet-badge", children: "Repaired" }) : null,
|
|
9510
|
+
invalid ? /* @__PURE__ */ jsx74("span", { className: "fdc-packet-badge is-danger", children: "Invalid" }) : null
|
|
9164
9511
|
] }),
|
|
9165
|
-
invalid ? /* @__PURE__ */
|
|
9166
|
-
/* @__PURE__ */
|
|
9167
|
-
/* @__PURE__ */
|
|
9168
|
-
] }) : /* @__PURE__ */
|
|
9169
|
-
!invalid && onApply ? /* @__PURE__ */
|
|
9170
|
-
packet.repaired ? /* @__PURE__ */
|
|
9171
|
-
/* @__PURE__ */
|
|
9172
|
-
/* @__PURE__ */
|
|
9512
|
+
invalid ? /* @__PURE__ */ jsxs70("div", { className: "fdc-packet-alert", role: "alert", children: [
|
|
9513
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
|
|
9514
|
+
/* @__PURE__ */ jsx74("span", { children: packet.error || "This result failed validation and can\u2019t be applied." })
|
|
9515
|
+
] }) : /* @__PURE__ */ jsx74("div", { className: "fdc-packet-body", children: render ? render(packet) : /* @__PURE__ */ jsx74(JsonView, { value: packet.payload }) }),
|
|
9516
|
+
!invalid && onApply ? /* @__PURE__ */ jsxs70("footer", { className: "fdc-packet-foot", children: [
|
|
9517
|
+
packet.repaired ? /* @__PURE__ */ jsx74("span", { className: "fdc-packet-note", children: "Corrected by the backend after a first attempt." }) : /* @__PURE__ */ jsx74("span", {}),
|
|
9518
|
+
/* @__PURE__ */ jsxs70("button", { type: "button", className: "fd-btn fd-btn-primary fd-btn-sm", disabled: applied, onClick: () => onApply(packet), children: [
|
|
9519
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-" + (applied ? "check" : "arrow-square-in"), "aria-hidden": "true" }),
|
|
9173
9520
|
applied ? "Applied" : s.applyLabel || "Apply"
|
|
9174
9521
|
] })
|
|
9175
9522
|
] }) : null
|
|
9176
9523
|
] });
|
|
9177
9524
|
}
|
|
9178
9525
|
function ThinkingBlock({ text, durationMs, streaming, defaultOpen = false }) {
|
|
9179
|
-
const [open, setOpen] =
|
|
9526
|
+
const [open, setOpen] = React46.useState(defaultOpen);
|
|
9180
9527
|
if (!text) return null;
|
|
9181
|
-
return /* @__PURE__ */
|
|
9182
|
-
/* @__PURE__ */
|
|
9183
|
-
/* @__PURE__ */
|
|
9184
|
-
/* @__PURE__ */
|
|
9185
|
-
streaming ? /* @__PURE__ */
|
|
9186
|
-
/* @__PURE__ */
|
|
9187
|
-
/* @__PURE__ */
|
|
9188
|
-
/* @__PURE__ */
|
|
9528
|
+
return /* @__PURE__ */ jsxs70("div", { className: "fdc-think" + (open ? " is-open" : ""), children: [
|
|
9529
|
+
/* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-think-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
|
|
9530
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-brain", "aria-hidden": "true" }),
|
|
9531
|
+
/* @__PURE__ */ jsx74("span", { children: streaming ? "Thinking" : durationMs ? "Thought for " + formatDuration(durationMs) : "Thought process" }),
|
|
9532
|
+
streaming ? /* @__PURE__ */ jsxs70("span", { className: "fdc-dots", "aria-hidden": "true", children: [
|
|
9533
|
+
/* @__PURE__ */ jsx74("span", {}),
|
|
9534
|
+
/* @__PURE__ */ jsx74("span", {}),
|
|
9535
|
+
/* @__PURE__ */ jsx74("span", {})
|
|
9189
9536
|
] }) : null,
|
|
9190
|
-
/* @__PURE__ */
|
|
9537
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-caret-" + (open ? "up" : "down"), "aria-hidden": "true" })
|
|
9191
9538
|
] }),
|
|
9192
|
-
open ? /* @__PURE__ */
|
|
9539
|
+
open ? /* @__PURE__ */ jsx74("div", { className: "fdc-think-body", children: text }) : null
|
|
9193
9540
|
] });
|
|
9194
9541
|
}
|
|
9195
9542
|
function Citations({ items = [], onOpen }) {
|
|
9196
|
-
const [open, setOpen] =
|
|
9543
|
+
const [open, setOpen] = React46.useState(false);
|
|
9197
9544
|
if (!items.length) return null;
|
|
9198
|
-
return /* @__PURE__ */
|
|
9199
|
-
/* @__PURE__ */
|
|
9200
|
-
/* @__PURE__ */
|
|
9201
|
-
/* @__PURE__ */
|
|
9545
|
+
return /* @__PURE__ */ jsxs70("div", { className: "fdc-cites", children: [
|
|
9546
|
+
/* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-cites-head", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [
|
|
9547
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-quotes", "aria-hidden": "true" }),
|
|
9548
|
+
/* @__PURE__ */ jsxs70("span", { children: [
|
|
9202
9549
|
items.length,
|
|
9203
9550
|
" source",
|
|
9204
9551
|
items.length === 1 ? "" : "s"
|
|
9205
9552
|
] }),
|
|
9206
|
-
/* @__PURE__ */
|
|
9553
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-caret-" + (open ? "up" : "down"), "aria-hidden": "true" })
|
|
9207
9554
|
] }),
|
|
9208
|
-
open ? /* @__PURE__ */
|
|
9209
|
-
/* @__PURE__ */
|
|
9210
|
-
c.url ? /* @__PURE__ */
|
|
9211
|
-
c.detail ? /* @__PURE__ */
|
|
9555
|
+
open ? /* @__PURE__ */ jsx74("ol", { className: "fdc-cites-list", children: items.map((c, i) => /* @__PURE__ */ jsxs70("li", { children: [
|
|
9556
|
+
/* @__PURE__ */ jsx74("span", { className: "fdc-cite-n fd-tabular", children: c.marker || i + 1 }),
|
|
9557
|
+
c.url ? /* @__PURE__ */ jsx74("a", { href: c.url, target: "_blank", rel: "noopener noreferrer", onClick: onOpen ? (e) => onOpen(c, e) : void 0, children: c.title || c.url }) : /* @__PURE__ */ jsx74("span", { children: c.title || "Source" }),
|
|
9558
|
+
c.detail ? /* @__PURE__ */ jsx74("span", { className: "fdc-cite-detail", children: c.detail }) : null
|
|
9212
9559
|
] }, c.id || i)) }) : null
|
|
9213
9560
|
] });
|
|
9214
9561
|
}
|
|
@@ -9219,29 +9566,29 @@ function clampText(text, max) {
|
|
|
9219
9566
|
return (at > max * 0.6 ? cut.slice(0, at) : cut).trimEnd() + "\u2026";
|
|
9220
9567
|
}
|
|
9221
9568
|
function MessageBody({ message: m, ctx }) {
|
|
9222
|
-
const [expanded, setExpanded] =
|
|
9569
|
+
const [expanded, setExpanded] = React46.useState(false);
|
|
9223
9570
|
const isUser = m.role === "user";
|
|
9224
9571
|
const raw = m.text || "";
|
|
9225
9572
|
const clamped = !expanded && !m.streaming ? clampText(raw, ctx.maxVisibleChars) : null;
|
|
9226
9573
|
const body = clamped != null ? clamped : raw;
|
|
9227
9574
|
const showMd = ctx.markdown && !isUser;
|
|
9228
|
-
return /* @__PURE__ */
|
|
9229
|
-
m.thinking && ctx.showThinking ? /* @__PURE__ */
|
|
9230
|
-
m.steps && m.steps.length && ctx.showSteps ? /* @__PURE__ */
|
|
9231
|
-
body ? /* @__PURE__ */
|
|
9232
|
-
showMd ? /* @__PURE__ */
|
|
9233
|
-
m.streaming ? /* @__PURE__ */
|
|
9575
|
+
return /* @__PURE__ */ jsxs70("div", { className: "fdc-body", children: [
|
|
9576
|
+
m.thinking && ctx.showThinking ? /* @__PURE__ */ jsx74(ThinkingBlock, { text: m.thinking, durationMs: m.thinkingMs, streaming: m.streaming && !m.text }) : null,
|
|
9577
|
+
m.steps && m.steps.length && ctx.showSteps ? /* @__PURE__ */ jsx74(StepList, { steps: m.steps, defaultOpen: m.steps.some((s) => s.status === "running"), dense: true }) : null,
|
|
9578
|
+
body ? /* @__PURE__ */ jsxs70("div", { className: "fdc-text" + (isUser ? " is-user" : ""), children: [
|
|
9579
|
+
showMd ? /* @__PURE__ */ jsx74(Markdown, { source: body, headingOffset: 2, codeProps: { collapseAfter: 22 }, renderCitation: ctx.renderCitation }) : /* @__PURE__ */ jsx74("div", { className: "fdc-plain", children: body }),
|
|
9580
|
+
m.streaming ? /* @__PURE__ */ jsx74("span", { className: "fdc-caret", "aria-hidden": "true" }) : null
|
|
9234
9581
|
] }) : null,
|
|
9235
|
-
clamped != null ? /* @__PURE__ */
|
|
9236
|
-
/* @__PURE__ */
|
|
9582
|
+
clamped != null ? /* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-more", onClick: () => setExpanded(true), children: [
|
|
9583
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-caret-down", "aria-hidden": "true" }),
|
|
9237
9584
|
"Show more",
|
|
9238
|
-
/* @__PURE__ */
|
|
9239
|
-
] }) : expanded && ctx.maxVisibleChars && raw.length > ctx.maxVisibleChars ? /* @__PURE__ */
|
|
9240
|
-
/* @__PURE__ */
|
|
9585
|
+
/* @__PURE__ */ jsx74("span", { className: "fdc-more-len fd-tabular", children: raw.length < 2e3 ? raw.length.toLocaleString() + " characters" : Math.round(raw.length / 100) / 10 + "k characters" })
|
|
9586
|
+
] }) : expanded && ctx.maxVisibleChars && raw.length > ctx.maxVisibleChars ? /* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-more", onClick: () => setExpanded(false), children: [
|
|
9587
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-caret-up", "aria-hidden": "true" }),
|
|
9241
9588
|
"Show less"
|
|
9242
9589
|
] }) : null,
|
|
9243
|
-
m.attachments && m.attachments.length ? /* @__PURE__ */
|
|
9244
|
-
m.packet ? /* @__PURE__ */
|
|
9590
|
+
m.attachments && m.attachments.length ? /* @__PURE__ */ jsx74(FileGrid, { files: m.attachments, onOpen: ctx.onOpenAttachment, tiles: true, maxHeight: ctx.attachmentHeight || 220, compact: ctx.narrow }) : null,
|
|
9591
|
+
m.packet ? /* @__PURE__ */ jsx74(
|
|
9245
9592
|
PacketCard,
|
|
9246
9593
|
{
|
|
9247
9594
|
packet: m.packet,
|
|
@@ -9251,44 +9598,44 @@ function MessageBody({ message: m, ctx }) {
|
|
|
9251
9598
|
applied: ctx.appliedPackets && ctx.appliedPackets[m.id]
|
|
9252
9599
|
}
|
|
9253
9600
|
) : null,
|
|
9254
|
-
m.citations && m.citations.length ? /* @__PURE__ */
|
|
9255
|
-
m.working ? /* @__PURE__ */
|
|
9256
|
-
/* @__PURE__ */
|
|
9257
|
-
/* @__PURE__ */
|
|
9258
|
-
/* @__PURE__ */
|
|
9259
|
-
/* @__PURE__ */
|
|
9601
|
+
m.citations && m.citations.length ? /* @__PURE__ */ jsx74(Citations, { items: m.citations, onOpen: ctx.onOpenCitation }) : null,
|
|
9602
|
+
m.working ? /* @__PURE__ */ jsxs70("div", { className: "fdc-working", role: "status", children: [
|
|
9603
|
+
/* @__PURE__ */ jsxs70("span", { className: "fdc-dots", "aria-hidden": "true", children: [
|
|
9604
|
+
/* @__PURE__ */ jsx74("span", {}),
|
|
9605
|
+
/* @__PURE__ */ jsx74("span", {}),
|
|
9606
|
+
/* @__PURE__ */ jsx74("span", {})
|
|
9260
9607
|
] }),
|
|
9261
|
-
/* @__PURE__ */
|
|
9608
|
+
/* @__PURE__ */ jsxs70("span", { className: "fdc-working-label", children: [
|
|
9262
9609
|
m.resumed ? "Resuming" : "Working",
|
|
9263
|
-
m.job && m.job.status ? /* @__PURE__ */
|
|
9610
|
+
m.job && m.job.status ? /* @__PURE__ */ jsxs70("span", { className: "fdc-working-job", children: [
|
|
9264
9611
|
" \xB7 ",
|
|
9265
9612
|
m.job.status
|
|
9266
9613
|
] }) : null,
|
|
9267
|
-
m.job && m.job.detail ? /* @__PURE__ */
|
|
9614
|
+
m.job && m.job.detail ? /* @__PURE__ */ jsxs70("span", { className: "fdc-working-job", children: [
|
|
9268
9615
|
" \xB7 ",
|
|
9269
9616
|
m.job.detail
|
|
9270
9617
|
] }) : null
|
|
9271
9618
|
] }),
|
|
9272
|
-
m.jobId ? /* @__PURE__ */
|
|
9619
|
+
m.jobId ? /* @__PURE__ */ jsx74("span", { className: "fdc-working-id fd-mono", children: String(m.jobId).slice(0, 12) }) : null
|
|
9273
9620
|
] }) : null,
|
|
9274
|
-
m.stopped ? /* @__PURE__ */
|
|
9275
|
-
/* @__PURE__ */
|
|
9621
|
+
m.stopped ? /* @__PURE__ */ jsxs70("div", { className: "fdc-stopped", children: [
|
|
9622
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-stop-circle", "aria-hidden": "true" }),
|
|
9276
9623
|
"Stopped"
|
|
9277
9624
|
] }) : null,
|
|
9278
|
-
m.error ? /* @__PURE__ */
|
|
9279
|
-
/* @__PURE__ */
|
|
9280
|
-
/* @__PURE__ */
|
|
9281
|
-
m.retryable && ctx.onRetry ? /* @__PURE__ */
|
|
9282
|
-
/* @__PURE__ */
|
|
9625
|
+
m.error ? /* @__PURE__ */ jsxs70("div", { className: "fdc-error", role: "alert", children: [
|
|
9626
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
|
|
9627
|
+
/* @__PURE__ */ jsx74("span", { className: "fdc-error-text", children: ctx.errorCopy ? ctx.errorCopy(m.error) : m.error }),
|
|
9628
|
+
m.retryable && ctx.onRetry ? /* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-error-retry", onClick: ctx.onRetry, children: [
|
|
9629
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-arrow-clockwise", "aria-hidden": "true" }),
|
|
9283
9630
|
"Retry"
|
|
9284
9631
|
] }) : null
|
|
9285
9632
|
] }) : null
|
|
9286
9633
|
] });
|
|
9287
9634
|
}
|
|
9288
9635
|
function useCopyRun() {
|
|
9289
|
-
const [done, setDone] =
|
|
9290
|
-
const t =
|
|
9291
|
-
|
|
9636
|
+
const [done, setDone] = React46.useState(false);
|
|
9637
|
+
const t = React46.useRef(null);
|
|
9638
|
+
React46.useEffect(() => () => {
|
|
9292
9639
|
if (t.current) clearTimeout(t.current);
|
|
9293
9640
|
}, []);
|
|
9294
9641
|
return [done, (text) => {
|
|
@@ -9308,16 +9655,16 @@ function RunActions({ group, ctx }) {
|
|
|
9308
9655
|
const isAssistant = group.role === "assistant";
|
|
9309
9656
|
const fb = last.feedback;
|
|
9310
9657
|
if (!ctx.messageActions) return null;
|
|
9311
|
-
return /* @__PURE__ */
|
|
9312
|
-
/* @__PURE__ */
|
|
9313
|
-
/* @__PURE__ */
|
|
9314
|
-
/* @__PURE__ */
|
|
9658
|
+
return /* @__PURE__ */ jsxs70("div", { className: "fdc-actions", role: "group", "aria-label": "Message actions", children: [
|
|
9659
|
+
/* @__PURE__ */ jsxs70("button", { type: "button", className: "fdc-act" + (copied ? " is-done" : ""), onClick: () => copy(markdownToText(text)), "aria-label": "Copy message", children: [
|
|
9660
|
+
/* @__PURE__ */ jsx74("i", { className: "ph ph-" + (copied ? "check" : "copy"), "aria-hidden": "true" }),
|
|
9661
|
+
/* @__PURE__ */ jsx74("span", { className: "fdc-act-label", children: copied ? "Copied" : "Copy" })
|
|
9315
9662
|
] }),
|
|
9316
|
-
isAssistant && ctx.onRetry ? /* @__PURE__ */
|
|
9317
|
-
group.role === "user" && ctx.onEdit ? /* @__PURE__ */
|
|
9318
|
-
isAssistant && ctx.onFeedback ? /* @__PURE__ */
|
|
9319
|
-
/* @__PURE__ */
|
|
9320
|
-
/* @__PURE__ */
|
|
9663
|
+
isAssistant && ctx.onRetry ? /* @__PURE__ */ jsx74("button", { type: "button", className: "fdc-act", onClick: ctx.onRetry, "aria-label": "Retry this turn", children: /* @__PURE__ */ jsx74("i", { className: "ph ph-arrow-clockwise", "aria-hidden": "true" }) }) : null,
|
|
9664
|
+
group.role === "user" && ctx.onEdit ? /* @__PURE__ */ jsx74("button", { type: "button", className: "fdc-act", onClick: () => ctx.onEdit(last), "aria-label": "Edit and resend", children: /* @__PURE__ */ jsx74("i", { className: "ph ph-pencil-simple", "aria-hidden": "true" }) }) : null,
|
|
9665
|
+
isAssistant && ctx.onFeedback ? /* @__PURE__ */ jsxs70(React46.Fragment, { children: [
|
|
9666
|
+
/* @__PURE__ */ jsx74("button", { type: "button", className: "fdc-act" + (fb === "up" ? " is-on" : ""), onClick: () => ctx.onFeedback(last.id, "up"), "aria-pressed": fb === "up", "aria-label": "Good response", children: /* @__PURE__ */ jsx74("i", { className: "ph ph-thumbs-up", "aria-hidden": "true" }) }),
|
|
9667
|
+
/* @__PURE__ */ jsx74("button", { type: "button", className: "fdc-act" + (fb === "down" ? " is-on" : ""), onClick: () => ctx.onFeedback(last.id, "down"), "aria-pressed": fb === "down", "aria-label": "Bad response", children: /* @__PURE__ */ jsx74("i", { className: "ph ph-thumbs-down", "aria-hidden": "true" }) })
|
|
9321
9668
|
] }) : null,
|
|
9322
9669
|
ctx.extraActions ? ctx.extraActions(group) : null
|
|
9323
9670
|
] });
|
|
@@ -9327,22 +9674,22 @@ function ChatTurn({ group, ctx }) {
|
|
|
9327
9674
|
const name = isUser ? ctx.userName : group.author || ctx.assistantName;
|
|
9328
9675
|
const avatar = isUser ? ctx.userAvatar : ctx.assistantAvatar;
|
|
9329
9676
|
const stamp = group.messages[0].timestamp;
|
|
9330
|
-
return /* @__PURE__ */
|
|
9331
|
-
/* @__PURE__ */
|
|
9332
|
-
ctx.showAvatars ? /* @__PURE__ */
|
|
9333
|
-
/* @__PURE__ */
|
|
9334
|
-
stamp ? /* @__PURE__ */
|
|
9335
|
-
group.messages.some((m) => m.model) ? /* @__PURE__ */
|
|
9677
|
+
return /* @__PURE__ */ jsxs70("article", { className: "fdc-turn is-" + group.role, "aria-label": String(name) + (stamp ? " at " + formatClock(stamp) : ""), children: [
|
|
9678
|
+
/* @__PURE__ */ jsxs70("div", { className: "fdc-turn-head", children: [
|
|
9679
|
+
ctx.showAvatars ? /* @__PURE__ */ jsx74("span", { className: "fdc-avatar is-" + group.role, "aria-hidden": "true", children: avatar ? /* @__PURE__ */ jsx74("img", { src: avatar, alt: "" }) : isUser ? /* @__PURE__ */ jsx74("span", { className: "fdc-avatar-txt", children: (name || "You").slice(0, 1).toUpperCase() }) : /* @__PURE__ */ jsx74("i", { className: "ph ph-" + (ctx.assistantIcon || "sparkle") }) }) : null,
|
|
9680
|
+
/* @__PURE__ */ jsx74("span", { className: "fdc-who", children: name }),
|
|
9681
|
+
stamp ? /* @__PURE__ */ jsx74(RelativeTime, { className: "fdc-when", value: stamp }) : null,
|
|
9682
|
+
group.messages.some((m) => m.model) ? /* @__PURE__ */ jsx74("span", { className: "fdc-turn-model", children: group.messages.find((m) => m.model).model }) : null
|
|
9336
9683
|
] }),
|
|
9337
|
-
/* @__PURE__ */
|
|
9338
|
-
group.messages.map((m) => /* @__PURE__ */
|
|
9339
|
-
/* @__PURE__ */
|
|
9684
|
+
/* @__PURE__ */ jsxs70("div", { className: "fdc-turn-body", children: [
|
|
9685
|
+
group.messages.map((m) => /* @__PURE__ */ jsx74("div", { className: "fdc-msg" + (m.pending ? " is-pending" : ""), children: /* @__PURE__ */ jsx74(MessageBody, { message: m, ctx }) }, m.id)),
|
|
9686
|
+
/* @__PURE__ */ jsx74(RunActions, { group, ctx })
|
|
9340
9687
|
] })
|
|
9341
9688
|
] });
|
|
9342
9689
|
}
|
|
9343
9690
|
|
|
9344
9691
|
// src/components/chat/ChatTranscript.tsx
|
|
9345
|
-
import { jsx as
|
|
9692
|
+
import { jsx as jsx75, jsxs as jsxs71 } from "react/jsx-runtime";
|
|
9346
9693
|
var GROUP_WINDOW = 6e4;
|
|
9347
9694
|
var STICK_PX = 100;
|
|
9348
9695
|
function groupMessages(list) {
|
|
@@ -9375,25 +9722,25 @@ function dayLabel(key) {
|
|
|
9375
9722
|
}
|
|
9376
9723
|
function Suggestions({ items = [], onPick }) {
|
|
9377
9724
|
if (!items.length) return null;
|
|
9378
|
-
return /* @__PURE__ */
|
|
9725
|
+
return /* @__PURE__ */ jsx75("div", { className: "fdc-suggest", children: items.map((s, i) => {
|
|
9379
9726
|
const it = typeof s === "string" ? { label: s, text: s } : s;
|
|
9380
|
-
return /* @__PURE__ */
|
|
9381
|
-
it.icon ? /* @__PURE__ */
|
|
9382
|
-
/* @__PURE__ */
|
|
9383
|
-
/* @__PURE__ */
|
|
9384
|
-
it.description ? /* @__PURE__ */
|
|
9727
|
+
return /* @__PURE__ */ jsxs71("button", { type: "button", className: "fdc-suggest-item", onClick: () => onPick && onPick(it.text || it.label), children: [
|
|
9728
|
+
it.icon ? /* @__PURE__ */ jsx75("i", { className: "ph ph-" + it.icon, "aria-hidden": "true" }) : null,
|
|
9729
|
+
/* @__PURE__ */ jsxs71("span", { className: "fdc-suggest-text", children: [
|
|
9730
|
+
/* @__PURE__ */ jsx75("span", { className: "fdc-suggest-label", children: it.label }),
|
|
9731
|
+
it.description ? /* @__PURE__ */ jsx75("span", { className: "fdc-suggest-desc", children: it.description }) : null
|
|
9385
9732
|
] }),
|
|
9386
|
-
/* @__PURE__ */
|
|
9733
|
+
/* @__PURE__ */ jsx75("i", { className: "ph ph-arrow-up-right fdc-suggest-go", "aria-hidden": "true" })
|
|
9387
9734
|
] }, it.id || i);
|
|
9388
9735
|
}) });
|
|
9389
9736
|
}
|
|
9390
9737
|
function LoadingTurns() {
|
|
9391
|
-
return /* @__PURE__ */
|
|
9392
|
-
/* @__PURE__ */
|
|
9393
|
-
/* @__PURE__ */
|
|
9394
|
-
/* @__PURE__ */
|
|
9395
|
-
/* @__PURE__ */
|
|
9396
|
-
/* @__PURE__ */
|
|
9738
|
+
return /* @__PURE__ */ jsx75("div", { className: "fdc-skel", "aria-hidden": "true", children: [0, 1].map((i) => /* @__PURE__ */ jsxs71("div", { className: "fdc-skel-turn", children: [
|
|
9739
|
+
/* @__PURE__ */ jsx75("div", { className: "fd-skel fd-skel-circle", style: { width: 22, height: 22 } }),
|
|
9740
|
+
/* @__PURE__ */ jsxs71("div", { className: "fdc-skel-lines", children: [
|
|
9741
|
+
/* @__PURE__ */ jsx75("div", { className: "fd-skel", style: { width: i ? "62%" : "44%", height: 11 } }),
|
|
9742
|
+
/* @__PURE__ */ jsx75("div", { className: "fd-skel", style: { width: i ? "94%" : "78%", height: 11 } }),
|
|
9743
|
+
/* @__PURE__ */ jsx75("div", { className: "fd-skel", style: { width: i ? "71%" : "56%", height: 11 } })
|
|
9397
9744
|
] })
|
|
9398
9745
|
] }, i)) });
|
|
9399
9746
|
}
|
|
@@ -9410,10 +9757,10 @@ function ChatTranscript({
|
|
|
9410
9757
|
renderEmpty,
|
|
9411
9758
|
className = ""
|
|
9412
9759
|
}) {
|
|
9413
|
-
const scroller =
|
|
9414
|
-
const stick =
|
|
9415
|
-
const [pill, setPill] =
|
|
9416
|
-
const seen =
|
|
9760
|
+
const scroller = React47.useRef(null);
|
|
9761
|
+
const stick = React47.useRef(true);
|
|
9762
|
+
const [pill, setPill] = React47.useState(0);
|
|
9763
|
+
const seen = React47.useRef(0);
|
|
9417
9764
|
const toBottom = (smooth) => {
|
|
9418
9765
|
const el = scroller.current;
|
|
9419
9766
|
if (!el) return;
|
|
@@ -9432,7 +9779,7 @@ function ChatTranscript({
|
|
|
9432
9779
|
seen.current = messages.length;
|
|
9433
9780
|
}
|
|
9434
9781
|
};
|
|
9435
|
-
|
|
9782
|
+
React47.useLayoutEffect(() => {
|
|
9436
9783
|
const el = scroller.current;
|
|
9437
9784
|
if (!el) return;
|
|
9438
9785
|
if (stick.current) {
|
|
@@ -9440,7 +9787,7 @@ function ChatTranscript({
|
|
|
9440
9787
|
seen.current = messages.length;
|
|
9441
9788
|
} else setPill(Math.max(0, messages.length - seen.current));
|
|
9442
9789
|
}, [messages]);
|
|
9443
|
-
|
|
9790
|
+
React47.useEffect(() => {
|
|
9444
9791
|
const el = scroller.current;
|
|
9445
9792
|
const inner = el && el.firstChild;
|
|
9446
9793
|
if (!el || !inner || typeof ResizeObserver === "undefined") return;
|
|
@@ -9450,38 +9797,38 @@ function ChatTranscript({
|
|
|
9450
9797
|
ro.observe(inner);
|
|
9451
9798
|
return () => ro.disconnect();
|
|
9452
9799
|
}, []);
|
|
9453
|
-
const groups =
|
|
9800
|
+
const groups = React47.useMemo(() => groupMessages(messages), [messages]);
|
|
9454
9801
|
const empty = !messages.length && status === "ready";
|
|
9455
|
-
return /* @__PURE__ */
|
|
9456
|
-
/* @__PURE__ */
|
|
9457
|
-
status === "loading" || status === "resolving" ? /* @__PURE__ */
|
|
9458
|
-
status === "disconnected" ? /* @__PURE__ */
|
|
9459
|
-
/* @__PURE__ */
|
|
9460
|
-
/* @__PURE__ */
|
|
9461
|
-
/* @__PURE__ */
|
|
9462
|
-
ctx.onReconnect ? /* @__PURE__ */
|
|
9463
|
-
/* @__PURE__ */
|
|
9802
|
+
return /* @__PURE__ */ jsxs71("div", { className: ["fdc-scroll", className].filter(Boolean).join(" "), ref: scroller, onScroll, children: [
|
|
9803
|
+
/* @__PURE__ */ jsxs71("div", { className: "fdc-log", role: "log", "aria-label": "Conversation", children: [
|
|
9804
|
+
status === "loading" || status === "resolving" ? /* @__PURE__ */ jsx75(LoadingTurns, {}) : null,
|
|
9805
|
+
status === "disconnected" ? /* @__PURE__ */ jsxs71("div", { className: "fdc-dead", children: [
|
|
9806
|
+
/* @__PURE__ */ jsx75("span", { className: "fdc-dead-icon", children: /* @__PURE__ */ jsx75("i", { className: "ph ph-plugs", "aria-hidden": "true" }) }),
|
|
9807
|
+
/* @__PURE__ */ jsx75("h3", { className: "fdc-dead-title", children: ctx.deadTitle || "The assistant isn\u2019t reachable" }),
|
|
9808
|
+
/* @__PURE__ */ jsx75("p", { className: "fdc-dead-body", children: fatal === "chat_unavailable" ? "The service reported chat_unavailable. Nothing you type will be lost \u2014 reopen the panel once it\u2019s back." : "The thread couldn\u2019t be resolved" + (fatal ? " (" + fatal + ")" : "") + ". The composer stays disabled until it resolves." }),
|
|
9809
|
+
ctx.onReconnect ? /* @__PURE__ */ jsxs71("button", { type: "button", className: "fd-btn fd-btn-secondary fd-btn-sm", onClick: ctx.onReconnect, children: [
|
|
9810
|
+
/* @__PURE__ */ jsx75("i", { className: "ph ph-arrow-clockwise", "aria-hidden": "true" }),
|
|
9464
9811
|
"Try again"
|
|
9465
9812
|
] }) : null
|
|
9466
9813
|
] }) : null,
|
|
9467
|
-
empty ? renderEmpty ? renderEmpty() : /* @__PURE__ */
|
|
9468
|
-
/* @__PURE__ */
|
|
9469
|
-
/* @__PURE__ */
|
|
9470
|
-
emptyDescription ? /* @__PURE__ */
|
|
9471
|
-
/* @__PURE__ */
|
|
9814
|
+
empty ? renderEmpty ? renderEmpty() : /* @__PURE__ */ jsxs71("div", { className: "fdc-empty", children: [
|
|
9815
|
+
/* @__PURE__ */ jsx75("span", { className: "fdc-empty-icon", children: /* @__PURE__ */ jsx75("i", { className: "ph ph-" + emptyIcon, "aria-hidden": "true" }) }),
|
|
9816
|
+
/* @__PURE__ */ jsx75("h3", { className: "fdc-empty-title", children: emptyTitle }),
|
|
9817
|
+
emptyDescription ? /* @__PURE__ */ jsx75("p", { className: "fdc-empty-body", children: emptyDescription }) : null,
|
|
9818
|
+
/* @__PURE__ */ jsx75(Suggestions, { items: suggestions, onPick })
|
|
9472
9819
|
] }) : null,
|
|
9473
9820
|
groups.map((g, i) => {
|
|
9474
9821
|
const prev = groups[i - 1];
|
|
9475
9822
|
const k = dayKey(g.messages[0].timestamp);
|
|
9476
9823
|
const showDay = !!k && (!prev || dayKey(prev.messages[0].timestamp) !== k);
|
|
9477
|
-
return /* @__PURE__ */
|
|
9478
|
-
showDay ? /* @__PURE__ */
|
|
9479
|
-
/* @__PURE__ */
|
|
9824
|
+
return /* @__PURE__ */ jsxs71(React47.Fragment, { children: [
|
|
9825
|
+
showDay ? /* @__PURE__ */ jsx75("div", { className: "fdc-day", children: /* @__PURE__ */ jsx75("span", { children: dayLabel(k) }) }) : null,
|
|
9826
|
+
/* @__PURE__ */ jsx75(ChatTurn, { group: g, ctx })
|
|
9480
9827
|
] }, g.key || i);
|
|
9481
9828
|
})
|
|
9482
9829
|
] }),
|
|
9483
|
-
pill ? /* @__PURE__ */
|
|
9484
|
-
/* @__PURE__ */
|
|
9830
|
+
pill ? /* @__PURE__ */ jsxs71("button", { type: "button", className: "fdc-pill", onClick: () => toBottom(true), children: [
|
|
9831
|
+
/* @__PURE__ */ jsx75("i", { className: "ph ph-arrow-down", "aria-hidden": "true" }),
|
|
9485
9832
|
pill,
|
|
9486
9833
|
" new message",
|
|
9487
9834
|
pill === 1 ? "" : "s"
|
|
@@ -9491,8 +9838,8 @@ function ChatTranscript({
|
|
|
9491
9838
|
var TranscriptKit = { groupMessages };
|
|
9492
9839
|
|
|
9493
9840
|
// src/components/chat/ChatComposer.tsx
|
|
9494
|
-
import * as
|
|
9495
|
-
import { jsx as
|
|
9841
|
+
import * as React48 from "react";
|
|
9842
|
+
import { jsx as jsx76, jsxs as jsxs72 } from "react/jsx-runtime";
|
|
9496
9843
|
function ChatComposer({
|
|
9497
9844
|
onSubmit,
|
|
9498
9845
|
onStop,
|
|
@@ -9519,17 +9866,17 @@ function ChatComposer({
|
|
|
9519
9866
|
onReject,
|
|
9520
9867
|
onOpenAttachment
|
|
9521
9868
|
}) {
|
|
9522
|
-
const [text, setText] =
|
|
9523
|
-
const [trigger, setTrigger] =
|
|
9524
|
-
const [mentionItems, setMentionItems] =
|
|
9525
|
-
const [listening, setListening] =
|
|
9526
|
-
const [notice, setNotice] =
|
|
9527
|
-
const editor =
|
|
9528
|
-
const wrap =
|
|
9529
|
-
const stopVoice =
|
|
9869
|
+
const [text, setText] = React48.useState(draft || "");
|
|
9870
|
+
const [trigger, setTrigger] = React48.useState(null);
|
|
9871
|
+
const [mentionItems, setMentionItems] = React48.useState([]);
|
|
9872
|
+
const [listening, setListening] = React48.useState(false);
|
|
9873
|
+
const [notice, setNotice] = React48.useState(null);
|
|
9874
|
+
const editor = React48.useRef(null);
|
|
9875
|
+
const wrap = React48.useRef(null);
|
|
9876
|
+
const stopVoice = React48.useRef(null);
|
|
9530
9877
|
const staged = useStagedFiles(fileUploadHandler, { onError: () => {
|
|
9531
9878
|
} });
|
|
9532
|
-
|
|
9879
|
+
React48.useEffect(() => {
|
|
9533
9880
|
if (draft != null && draft !== text) setText(draft);
|
|
9534
9881
|
}, [draft]);
|
|
9535
9882
|
const change = (v) => {
|
|
@@ -9565,7 +9912,7 @@ function ChatComposer({
|
|
|
9565
9912
|
e.preventDefault();
|
|
9566
9913
|
staged.add(found.files);
|
|
9567
9914
|
};
|
|
9568
|
-
|
|
9915
|
+
React48.useEffect(() => {
|
|
9569
9916
|
if (!trigger || trigger.type !== "mention" || !mentionSources) {
|
|
9570
9917
|
setMentionItems([]);
|
|
9571
9918
|
return;
|
|
@@ -9583,7 +9930,7 @@ function ChatComposer({
|
|
|
9583
9930
|
const q = (trigger.query || "").toLowerCase();
|
|
9584
9931
|
setMentionItems(mentionSources.filter((m) => !q || (m.label + " " + (m.description || "")).toLowerCase().includes(q)));
|
|
9585
9932
|
}, [trigger, mentionSources]);
|
|
9586
|
-
const slashItems =
|
|
9933
|
+
const slashItems = React48.useMemo(() => {
|
|
9587
9934
|
if (!trigger || trigger.type !== "slash" || !slashCommands) return [];
|
|
9588
9935
|
const q = (trigger.query || "").toLowerCase();
|
|
9589
9936
|
return slashCommands.filter((c) => !q || (c.id + " " + c.label + " " + (c.description || "")).toLowerCase().includes(q));
|
|
@@ -9597,7 +9944,7 @@ function ChatComposer({
|
|
|
9597
9944
|
description: it.description,
|
|
9598
9945
|
icon: it.icon,
|
|
9599
9946
|
meta: mention.meta,
|
|
9600
|
-
shortcut: slash.shortcut ? /* @__PURE__ */
|
|
9947
|
+
shortcut: slash.shortcut ? /* @__PURE__ */ jsx76(KeyHint, { keys: slash.shortcut, size: "sm" }) : void 0,
|
|
9601
9948
|
onSelect: () => {
|
|
9602
9949
|
const insert = trigger.type === "slash" ? slash.immediate ? "/" + slash.id : "/" + slash.id + " " : "@" + (mention.value || mention.label) + " ";
|
|
9603
9950
|
editor.current && editor.current.replaceRange(trigger.from, trigger.to, insert);
|
|
@@ -9660,14 +10007,14 @@ function ChatComposer({
|
|
|
9660
10007
|
stopVoice.current = typeof res === "function" ? res : () => {
|
|
9661
10008
|
};
|
|
9662
10009
|
};
|
|
9663
|
-
return /* @__PURE__ */
|
|
9664
|
-
queue.length ? /* @__PURE__ */
|
|
9665
|
-
/* @__PURE__ */
|
|
9666
|
-
/* @__PURE__ */
|
|
9667
|
-
onRemoveQueued ? /* @__PURE__ */
|
|
10010
|
+
return /* @__PURE__ */ jsxs72("div", { className: "fdc-composer" + (disabled ? " is-disabled" : "") + (narrow ? " is-narrow" : ""), children: [
|
|
10011
|
+
queue.length ? /* @__PURE__ */ jsx76("div", { className: "fdc-queue", "aria-label": "Queued messages", children: queue.map((q) => /* @__PURE__ */ jsxs72("div", { className: "fdc-queue-row", children: [
|
|
10012
|
+
/* @__PURE__ */ jsx76("i", { className: "ph ph-clock-countdown", "aria-hidden": "true" }),
|
|
10013
|
+
/* @__PURE__ */ jsx76("span", { className: "fdc-queue-text", title: truncationTitle(q.text || (q.attachments ? q.attachments.length + " file(s)" : "")), children: q.text || (q.attachments ? q.attachments.length + " file(s)" : "") }),
|
|
10014
|
+
onRemoveQueued ? /* @__PURE__ */ jsx76("button", { type: "button", className: "fdc-queue-x", onClick: () => onRemoveQueued(q.id), "aria-label": "Remove queued message", children: /* @__PURE__ */ jsx76("i", { className: "ph ph-x", "aria-hidden": "true" }) }) : null
|
|
9668
10015
|
] }, q.id)) }) : null,
|
|
9669
10016
|
sessionBar,
|
|
9670
|
-
/* @__PURE__ */
|
|
10017
|
+
/* @__PURE__ */ jsx76(
|
|
9671
10018
|
Dropzone,
|
|
9672
10019
|
{
|
|
9673
10020
|
className: "fdc-field",
|
|
@@ -9681,9 +10028,9 @@ function ChatComposer({
|
|
|
9681
10028
|
disabled: !fileUploadHandler || disabled,
|
|
9682
10029
|
label: "Drop to attach",
|
|
9683
10030
|
hint: acceptFiles ? acceptFiles.replace(/,/g, " \xB7 ") : void 0,
|
|
9684
|
-
children: /* @__PURE__ */
|
|
9685
|
-
staged.items.length ? /* @__PURE__ */
|
|
9686
|
-
/* @__PURE__ */
|
|
10031
|
+
children: /* @__PURE__ */ jsxs72("div", { ref: wrap, className: "fdc-field-inner", children: [
|
|
10032
|
+
staged.items.length ? /* @__PURE__ */ jsx76("div", { className: "fdc-staged", children: /* @__PURE__ */ jsx76(FileStrip, { files: staged.items, size: narrow ? 60 : 68, onRemove: staged.remove, onRetry: staged.retry, onOpen: onOpenAttachment }) }) : null,
|
|
10033
|
+
/* @__PURE__ */ jsx76(
|
|
9687
10034
|
MarkdownEditor,
|
|
9688
10035
|
{
|
|
9689
10036
|
ref: editor,
|
|
@@ -9702,33 +10049,33 @@ function ChatComposer({
|
|
|
9702
10049
|
ariaLabel: "Message"
|
|
9703
10050
|
}
|
|
9704
10051
|
),
|
|
9705
|
-
/* @__PURE__ */
|
|
9706
|
-
fileUploadHandler ? /* @__PURE__ */
|
|
9707
|
-
voiceHandler ? /* @__PURE__ */
|
|
10052
|
+
/* @__PURE__ */ jsxs72("div", { className: "fdc-tools", children: [
|
|
10053
|
+
fileUploadHandler ? /* @__PURE__ */ jsx76(FilePickButton, { onFiles: staged.add, onReject: (r) => flash(r[0].message), accept: acceptFiles, maxFileSize, label: "Attach files", icon: "plus" }) : null,
|
|
10054
|
+
voiceHandler ? /* @__PURE__ */ jsx76("button", { type: "button", className: "fd-attachbtn" + (listening ? " is-live" : ""), onClick: voice, "aria-pressed": listening, "aria-label": listening ? "Stop dictation" : "Dictate", children: /* @__PURE__ */ jsx76("i", { className: "ph ph-" + (listening ? "waveform" : "microphone"), "aria-hidden": "true" }) }) : null,
|
|
9708
10055
|
toolbarExtras,
|
|
9709
|
-
/* @__PURE__ */
|
|
9710
|
-
maxLength && text.length > maxLength * 0.6 ? /* @__PURE__ */
|
|
10056
|
+
/* @__PURE__ */ jsx76("span", { className: "fdc-tools-gap" }),
|
|
10057
|
+
maxLength && text.length > maxLength * 0.6 ? /* @__PURE__ */ jsxs72("span", { className: "fdc-count fd-tabular" + (text.length > maxLength * 0.95 ? " is-hot" : ""), children: [
|
|
9711
10058
|
text.length,
|
|
9712
10059
|
"/",
|
|
9713
10060
|
maxLength
|
|
9714
10061
|
] }) : null,
|
|
9715
|
-
busy ? /* @__PURE__ */
|
|
9716
|
-
/* @__PURE__ */
|
|
9717
|
-
/* @__PURE__ */
|
|
9718
|
-
] }) : /* @__PURE__ */
|
|
9719
|
-
/* @__PURE__ */
|
|
9720
|
-
/* @__PURE__ */
|
|
10062
|
+
busy ? /* @__PURE__ */ jsxs72("button", { type: "button", className: "fdc-stop", onClick: onStop, "aria-label": "Stop generating", children: [
|
|
10063
|
+
/* @__PURE__ */ jsx76("i", { className: "ph ph-stop-circle", "aria-hidden": "true" }),
|
|
10064
|
+
/* @__PURE__ */ jsx76("span", { children: "Stop" })
|
|
10065
|
+
] }) : /* @__PURE__ */ jsxs72("button", { type: "button", className: "fdc-send", onClick: submit, disabled: !canSend, "aria-label": "Send message", children: [
|
|
10066
|
+
/* @__PURE__ */ jsx76("i", { className: "ph ph-paper-plane-right", "aria-hidden": "true" }),
|
|
10067
|
+
/* @__PURE__ */ jsx76("span", { className: "fdc-send-label", children: "Send" })
|
|
9721
10068
|
] })
|
|
9722
10069
|
] })
|
|
9723
10070
|
] })
|
|
9724
10071
|
}
|
|
9725
10072
|
),
|
|
9726
|
-
notice ? /* @__PURE__ */
|
|
9727
|
-
/* @__PURE__ */
|
|
10073
|
+
notice ? /* @__PURE__ */ jsxs72("div", { className: "fdc-notice", role: "status", children: [
|
|
10074
|
+
/* @__PURE__ */ jsx76("i", { className: "ph ph-warning-circle", "aria-hidden": "true" }),
|
|
9728
10075
|
notice
|
|
9729
10076
|
] }) : null,
|
|
9730
|
-
hint && !notice ? /* @__PURE__ */
|
|
9731
|
-
/* @__PURE__ */
|
|
10077
|
+
hint && !notice ? /* @__PURE__ */ jsx76("div", { className: "fdc-hint", children: hint }) : null,
|
|
10078
|
+
/* @__PURE__ */ jsx76(
|
|
9732
10079
|
Popover,
|
|
9733
10080
|
{
|
|
9734
10081
|
open: menuOpen,
|
|
@@ -9742,12 +10089,12 @@ function ChatComposer({
|
|
|
9742
10089
|
returnFocus: false,
|
|
9743
10090
|
closeOnOutside: true,
|
|
9744
10091
|
label: trigger && trigger.type === "slash" ? "Commands" : "Mentions",
|
|
9745
|
-
children: /* @__PURE__ */
|
|
10092
|
+
children: /* @__PURE__ */ jsx76(
|
|
9746
10093
|
Menu,
|
|
9747
10094
|
{
|
|
9748
10095
|
items: menuItems,
|
|
9749
10096
|
autoFocus: false,
|
|
9750
|
-
header: /* @__PURE__ */
|
|
10097
|
+
header: /* @__PURE__ */ jsx76("div", { className: "fd-pop-group", children: trigger && trigger.type === "slash" ? "Commands" : "Attach context" }),
|
|
9751
10098
|
onClose: () => setTrigger(null)
|
|
9752
10099
|
}
|
|
9753
10100
|
)
|
|
@@ -9757,12 +10104,12 @@ function ChatComposer({
|
|
|
9757
10104
|
}
|
|
9758
10105
|
|
|
9759
10106
|
// src/components/chat/ChatSessionBar.tsx
|
|
9760
|
-
import * as
|
|
9761
|
-
import { jsx as
|
|
10107
|
+
import * as React49 from "react";
|
|
10108
|
+
import { jsx as jsx77, jsxs as jsxs73 } from "react/jsx-runtime";
|
|
9762
10109
|
var compact2 = meterFormats.compact;
|
|
9763
10110
|
function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extras }) {
|
|
9764
|
-
const [open, setOpen] =
|
|
9765
|
-
const anchor =
|
|
10111
|
+
const [open, setOpen] = React49.useState(false);
|
|
10112
|
+
const anchor = React49.useRef(null);
|
|
9766
10113
|
const stats = sessionStats || null;
|
|
9767
10114
|
const cu = contextUsage || null;
|
|
9768
10115
|
const limits = usageLimits || null;
|
|
@@ -9777,9 +10124,9 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
|
|
|
9777
10124
|
if (stats && stats.runningTasks) bits.push(stats.runningTasks + " running task" + (stats.runningTasks === 1 ? "" : "s"));
|
|
9778
10125
|
if (cu) bits.push(pct + "% context");
|
|
9779
10126
|
const expandable = !!(cu || limits);
|
|
9780
|
-
return /* @__PURE__ */
|
|
9781
|
-
/* @__PURE__ */
|
|
9782
|
-
/* @__PURE__ */
|
|
10127
|
+
return /* @__PURE__ */ jsxs73(React49.Fragment, { children: [
|
|
10128
|
+
/* @__PURE__ */ jsxs73("div", { className: "fdc-bar", children: [
|
|
10129
|
+
/* @__PURE__ */ jsxs73(
|
|
9783
10130
|
"button",
|
|
9784
10131
|
{
|
|
9785
10132
|
type: "button",
|
|
@@ -9790,15 +10137,15 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
|
|
|
9790
10137
|
"aria-expanded": expandable ? open : void 0,
|
|
9791
10138
|
"aria-label": expandable ? "Usage details" : void 0,
|
|
9792
10139
|
children: [
|
|
9793
|
-
cu ? /* @__PURE__ */
|
|
9794
|
-
/* @__PURE__ */
|
|
9795
|
-
expandable ? /* @__PURE__ */
|
|
10140
|
+
cu ? /* @__PURE__ */ jsx77("span", { className: "fdc-bar-mini", "aria-hidden": "true", children: /* @__PURE__ */ jsx77(SegmentedMeter, { total, segments: cu.segments, height: 4, showTotal: false }) }) : null,
|
|
10141
|
+
/* @__PURE__ */ jsx77("span", { className: "fdc-bar-text", title: truncationTitle(bits.join(" \xB7 ")), children: bits.join(" \xB7 ") }),
|
|
10142
|
+
expandable ? /* @__PURE__ */ jsx77("i", { className: "ph ph-caret-right fdc-bar-caret", "aria-hidden": "true" }) : null
|
|
9796
10143
|
]
|
|
9797
10144
|
}
|
|
9798
10145
|
),
|
|
9799
10146
|
extras
|
|
9800
10147
|
] }),
|
|
9801
|
-
/* @__PURE__ */
|
|
10148
|
+
/* @__PURE__ */ jsx77(
|
|
9802
10149
|
Popover,
|
|
9803
10150
|
{
|
|
9804
10151
|
open,
|
|
@@ -9809,15 +10156,15 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
|
|
|
9809
10156
|
maxHeight: 460,
|
|
9810
10157
|
padded: true,
|
|
9811
10158
|
label: "Usage",
|
|
9812
|
-
footer: onClear ? /* @__PURE__ */
|
|
10159
|
+
footer: onClear ? /* @__PURE__ */ jsxs73("button", { type: "button", className: "fdc-usage-clear", onClick: () => {
|
|
9813
10160
|
setOpen(false);
|
|
9814
10161
|
onClear();
|
|
9815
10162
|
}, children: [
|
|
9816
|
-
/* @__PURE__ */
|
|
10163
|
+
/* @__PURE__ */ jsx77("i", { className: "ph ph-trash", "aria-hidden": "true" }),
|
|
9817
10164
|
"Clear conversation"
|
|
9818
10165
|
] }) : void 0,
|
|
9819
|
-
children: /* @__PURE__ */
|
|
9820
|
-
cu ? /* @__PURE__ */
|
|
10166
|
+
children: /* @__PURE__ */ jsxs73("div", { className: "fdc-usage", children: [
|
|
10167
|
+
cu ? /* @__PURE__ */ jsx77("section", { className: "fdc-usage-sec", children: /* @__PURE__ */ jsx77(
|
|
9821
10168
|
SegmentedMeter,
|
|
9822
10169
|
{
|
|
9823
10170
|
total,
|
|
@@ -9829,9 +10176,9 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
|
|
|
9829
10176
|
remainderLabel: "Free"
|
|
9830
10177
|
}
|
|
9831
10178
|
) }) : null,
|
|
9832
|
-
limits && limits.length ? /* @__PURE__ */
|
|
9833
|
-
/* @__PURE__ */
|
|
9834
|
-
/* @__PURE__ */
|
|
10179
|
+
limits && limits.length ? /* @__PURE__ */ jsxs73("section", { className: "fdc-usage-sec", children: [
|
|
10180
|
+
/* @__PURE__ */ jsx77("h4", { className: "fdc-usage-h", children: "Usage limits" }),
|
|
10181
|
+
/* @__PURE__ */ jsx77("div", { className: "fdc-usage-rows", children: limits.map((l) => /* @__PURE__ */ jsx77(
|
|
9835
10182
|
QuotaRow,
|
|
9836
10183
|
{
|
|
9837
10184
|
label: l.label,
|
|
@@ -9841,25 +10188,25 @@ function ChatSessionBar({ sessionStats, contextUsage, usageLimits, onClear, extr
|
|
|
9841
10188
|
l.id
|
|
9842
10189
|
)) })
|
|
9843
10190
|
] }) : null,
|
|
9844
|
-
stats ? /* @__PURE__ */
|
|
9845
|
-
stats.elapsedMs ? /* @__PURE__ */
|
|
9846
|
-
/* @__PURE__ */
|
|
9847
|
-
/* @__PURE__ */
|
|
10191
|
+
stats ? /* @__PURE__ */ jsxs73("section", { className: "fdc-usage-sec fdc-usage-stats", children: [
|
|
10192
|
+
stats.elapsedMs ? /* @__PURE__ */ jsxs73("div", { children: [
|
|
10193
|
+
/* @__PURE__ */ jsx77("span", { children: "Session" }),
|
|
10194
|
+
/* @__PURE__ */ jsx77("b", { className: "fd-tabular", children: formatDuration(stats.elapsedMs) })
|
|
9848
10195
|
] }) : null,
|
|
9849
|
-
stats.tokens ? /* @__PURE__ */
|
|
9850
|
-
/* @__PURE__ */
|
|
9851
|
-
/* @__PURE__ */
|
|
10196
|
+
stats.tokens ? /* @__PURE__ */ jsxs73("div", { children: [
|
|
10197
|
+
/* @__PURE__ */ jsx77("span", { children: "Tokens" }),
|
|
10198
|
+
/* @__PURE__ */ jsx77("b", { className: "fd-tabular", children: stats.tokens.toLocaleString() })
|
|
9852
10199
|
] }) : null,
|
|
9853
|
-
stats.costUsd != null ? /* @__PURE__ */
|
|
9854
|
-
/* @__PURE__ */
|
|
9855
|
-
/* @__PURE__ */
|
|
10200
|
+
stats.costUsd != null ? /* @__PURE__ */ jsxs73("div", { children: [
|
|
10201
|
+
/* @__PURE__ */ jsx77("span", { children: "Cost" }),
|
|
10202
|
+
/* @__PURE__ */ jsxs73("b", { className: "fd-tabular", children: [
|
|
9856
10203
|
"$",
|
|
9857
10204
|
Number(stats.costUsd).toFixed(3)
|
|
9858
10205
|
] })
|
|
9859
10206
|
] }) : null,
|
|
9860
|
-
stats.turns ? /* @__PURE__ */
|
|
9861
|
-
/* @__PURE__ */
|
|
9862
|
-
/* @__PURE__ */
|
|
10207
|
+
stats.turns ? /* @__PURE__ */ jsxs73("div", { children: [
|
|
10208
|
+
/* @__PURE__ */ jsx77("span", { children: "Turns" }),
|
|
10209
|
+
/* @__PURE__ */ jsx77("b", { className: "fd-tabular", children: stats.turns })
|
|
9863
10210
|
] }) : null
|
|
9864
10211
|
] }) : null
|
|
9865
10212
|
] })
|
|
@@ -9898,7 +10245,7 @@ function ModelControls({
|
|
|
9898
10245
|
disabled: m.disabled,
|
|
9899
10246
|
checked: m.id === (current2 && current2.id),
|
|
9900
10247
|
meta: m.meta,
|
|
9901
|
-
shortcut: m.shortcut ? /* @__PURE__ */
|
|
10248
|
+
shortcut: m.shortcut ? /* @__PURE__ */ jsx77(KeyHint, { keys: m.shortcut, size: "sm" }) : void 0,
|
|
9902
10249
|
onSelect: () => onModelChange && onModelChange(m.id)
|
|
9903
10250
|
});
|
|
9904
10251
|
const items = [{ kind: "section", label: "Models" }].concat(flat.map(item));
|
|
@@ -9911,15 +10258,15 @@ function ModelControls({
|
|
|
9911
10258
|
items.push({ kind: "section", label: "Fast mode" });
|
|
9912
10259
|
items.push({
|
|
9913
10260
|
kind: "custom",
|
|
9914
|
-
render: () => /* @__PURE__ */
|
|
9915
|
-
/* @__PURE__ */
|
|
9916
|
-
/* @__PURE__ */
|
|
9917
|
-
/* @__PURE__ */
|
|
10261
|
+
render: () => /* @__PURE__ */ jsxs73("label", { className: "fdc-switchrow", children: [
|
|
10262
|
+
/* @__PURE__ */ jsx77("span", { children: fastModeLabel }),
|
|
10263
|
+
/* @__PURE__ */ jsx77("input", { type: "checkbox", className: "fd-sr", checked: !!fastMode, onChange: (e) => onFastModeChange(e.target.checked) }),
|
|
10264
|
+
/* @__PURE__ */ jsx77("span", { className: "fd-switch-track" + (fastMode ? " is-on" : ""), "aria-hidden": "true", children: /* @__PURE__ */ jsx77("span", { className: "fd-switch-thumb" }) })
|
|
9918
10265
|
] })
|
|
9919
10266
|
});
|
|
9920
10267
|
}
|
|
9921
|
-
return /* @__PURE__ */
|
|
9922
|
-
/* @__PURE__ */
|
|
10268
|
+
return /* @__PURE__ */ jsxs73(React49.Fragment, { children: [
|
|
10269
|
+
/* @__PURE__ */ jsx77(
|
|
9923
10270
|
MenuButton,
|
|
9924
10271
|
{
|
|
9925
10272
|
items,
|
|
@@ -9930,7 +10277,7 @@ function ModelControls({
|
|
|
9930
10277
|
title: "Choose a model"
|
|
9931
10278
|
}
|
|
9932
10279
|
),
|
|
9933
|
-
effortLevels && effortLevels.length ? /* @__PURE__ */
|
|
10280
|
+
effortLevels && effortLevels.length ? /* @__PURE__ */ jsx77(
|
|
9934
10281
|
MenuButton,
|
|
9935
10282
|
{
|
|
9936
10283
|
placement: "top-end",
|
|
@@ -9951,7 +10298,7 @@ function ModelControls({
|
|
|
9951
10298
|
}
|
|
9952
10299
|
|
|
9953
10300
|
// src/components/chat/AgentChatPanel.tsx
|
|
9954
|
-
import { jsx as
|
|
10301
|
+
import { jsx as jsx78, jsxs as jsxs74 } from "react/jsx-runtime";
|
|
9955
10302
|
var SURFACES = { sidebar: "is-sidebar", inline: "is-inline", page: "is-page", modal: "is-modal", sheet: "is-sheet" };
|
|
9956
10303
|
function AgentChatPanel({
|
|
9957
10304
|
/* required */
|
|
@@ -10035,11 +10382,11 @@ function AgentChatPanel({
|
|
|
10035
10382
|
onFeedback,
|
|
10036
10383
|
onEditMessage
|
|
10037
10384
|
}) {
|
|
10038
|
-
const [panelWidth, setPanelWidth] =
|
|
10039
|
-
const [applied, setApplied] =
|
|
10040
|
-
const [draft, setDraft] =
|
|
10041
|
-
const dragging =
|
|
10042
|
-
|
|
10385
|
+
const [panelWidth, setPanelWidth] = React50.useState(width);
|
|
10386
|
+
const [applied, setApplied] = React50.useState({});
|
|
10387
|
+
const [draft, setDraft] = React50.useState("");
|
|
10388
|
+
const dragging = React50.useRef(null);
|
|
10389
|
+
React50.useEffect(() => setPanelWidth(width), [width]);
|
|
10043
10390
|
const engine = useChatEngine({
|
|
10044
10391
|
contextType,
|
|
10045
10392
|
contextId,
|
|
@@ -10105,7 +10452,7 @@ function AgentChatPanel({
|
|
|
10105
10452
|
onReconnect: engine.reload,
|
|
10106
10453
|
deadTitle: "The assistant isn\u2019t reachable"
|
|
10107
10454
|
};
|
|
10108
|
-
const sessionBar = /* @__PURE__ */
|
|
10455
|
+
const sessionBar = /* @__PURE__ */ jsx78(
|
|
10109
10456
|
ChatSessionBar,
|
|
10110
10457
|
{
|
|
10111
10458
|
sessionStats,
|
|
@@ -10114,7 +10461,7 @@ function AgentChatPanel({
|
|
|
10114
10461
|
onClear: engine.visible.length ? engine.clear : void 0
|
|
10115
10462
|
}
|
|
10116
10463
|
);
|
|
10117
|
-
const modelControls = /* @__PURE__ */
|
|
10464
|
+
const modelControls = /* @__PURE__ */ jsx78(
|
|
10118
10465
|
ModelControls,
|
|
10119
10466
|
{
|
|
10120
10467
|
models,
|
|
@@ -10129,7 +10476,7 @@ function AgentChatPanel({
|
|
|
10129
10476
|
narrow
|
|
10130
10477
|
}
|
|
10131
10478
|
);
|
|
10132
|
-
const threadMenu = threads && threads.length ? /* @__PURE__ */
|
|
10479
|
+
const threadMenu = threads && threads.length ? /* @__PURE__ */ jsx78(
|
|
10133
10480
|
MenuButton,
|
|
10134
10481
|
{
|
|
10135
10482
|
variant: "ghost",
|
|
@@ -10156,7 +10503,7 @@ function AgentChatPanel({
|
|
|
10156
10503
|
})))
|
|
10157
10504
|
}
|
|
10158
10505
|
) : null;
|
|
10159
|
-
return /* @__PURE__ */
|
|
10506
|
+
return /* @__PURE__ */ jsxs74(
|
|
10160
10507
|
"aside",
|
|
10161
10508
|
{
|
|
10162
10509
|
className: ["fdc-panel", SURFACES[surface] || SURFACES.sidebar, narrow ? "is-narrow" : "", className].filter(Boolean).join(" "),
|
|
@@ -10167,7 +10514,7 @@ function AgentChatPanel({
|
|
|
10167
10514
|
},
|
|
10168
10515
|
"aria-label": title,
|
|
10169
10516
|
children: [
|
|
10170
|
-
surface === "sidebar" && resizable ? /* @__PURE__ */
|
|
10517
|
+
surface === "sidebar" && resizable ? /* @__PURE__ */ jsx78(
|
|
10171
10518
|
"div",
|
|
10172
10519
|
{
|
|
10173
10520
|
className: "fdc-grip",
|
|
@@ -10182,20 +10529,20 @@ function AgentChatPanel({
|
|
|
10182
10529
|
}
|
|
10183
10530
|
}
|
|
10184
10531
|
) : null,
|
|
10185
|
-
showHeader ? /* @__PURE__ */
|
|
10186
|
-
/* @__PURE__ */
|
|
10187
|
-
/* @__PURE__ */
|
|
10188
|
-
/* @__PURE__ */
|
|
10189
|
-
subtitle ? /* @__PURE__ */
|
|
10532
|
+
showHeader ? /* @__PURE__ */ jsxs74("header", { className: "fdc-head", children: [
|
|
10533
|
+
/* @__PURE__ */ jsx78("span", { className: "fdc-head-mark", "aria-hidden": "true", children: /* @__PURE__ */ jsx78("i", { className: "ph ph-" + assistantIcon }) }),
|
|
10534
|
+
/* @__PURE__ */ jsxs74("span", { className: "fdc-head-titles", children: [
|
|
10535
|
+
/* @__PURE__ */ jsx78("span", { className: "fdc-head-title", title: truncationTitle(title), children: title }),
|
|
10536
|
+
subtitle ? /* @__PURE__ */ jsx78("span", { className: "fdc-head-sub", title: truncationTitle(subtitle), children: subtitle }) : null
|
|
10190
10537
|
] }),
|
|
10191
|
-
/* @__PURE__ */
|
|
10538
|
+
/* @__PURE__ */ jsxs74("span", { className: "fdc-head-acts", children: [
|
|
10192
10539
|
headerActions,
|
|
10193
10540
|
threadMenu,
|
|
10194
|
-
onNewThread ? /* @__PURE__ */
|
|
10195
|
-
onClose ? /* @__PURE__ */
|
|
10541
|
+
onNewThread ? /* @__PURE__ */ jsx78("button", { type: "button", className: "fdc-iconbtn", onClick: onNewThread, "aria-label": "New conversation", title: "New conversation", children: /* @__PURE__ */ jsx78("i", { className: "ph ph-plus", "aria-hidden": "true" }) }) : null,
|
|
10542
|
+
onClose ? /* @__PURE__ */ jsx78("button", { type: "button", className: "fdc-iconbtn", onClick: onClose, "aria-label": "Close panel", title: "Close", children: /* @__PURE__ */ jsx78("i", { className: "ph ph-x", "aria-hidden": "true" }) }) : null
|
|
10196
10543
|
] })
|
|
10197
10544
|
] }) : null,
|
|
10198
|
-
/* @__PURE__ */
|
|
10545
|
+
/* @__PURE__ */ jsx78(
|
|
10199
10546
|
ChatTranscript,
|
|
10200
10547
|
{
|
|
10201
10548
|
messages: engine.visible,
|
|
@@ -10210,7 +10557,7 @@ function AgentChatPanel({
|
|
|
10210
10557
|
renderEmpty
|
|
10211
10558
|
}
|
|
10212
10559
|
),
|
|
10213
|
-
/* @__PURE__ */
|
|
10560
|
+
/* @__PURE__ */ jsx78(
|
|
10214
10561
|
ChatComposer,
|
|
10215
10562
|
{
|
|
10216
10563
|
onSubmit: (text, atts) => engine.send(text, atts),
|
|
@@ -10243,8 +10590,8 @@ function AgentChatPanel({
|
|
|
10243
10590
|
}
|
|
10244
10591
|
|
|
10245
10592
|
// src/components/files/FileViewer.tsx
|
|
10246
|
-
import * as
|
|
10247
|
-
import { jsx as
|
|
10593
|
+
import * as React51 from "react";
|
|
10594
|
+
import { jsx as jsx79, jsxs as jsxs75 } from "react/jsx-runtime";
|
|
10248
10595
|
function previewKindOf(file) {
|
|
10249
10596
|
const mime = (file.mime || "").toLowerCase();
|
|
10250
10597
|
const ext = extensionOf(file.name || "");
|
|
@@ -10300,13 +10647,13 @@ function parseDelimited(text, delimiter) {
|
|
|
10300
10647
|
return rows;
|
|
10301
10648
|
}
|
|
10302
10649
|
function usePreview(file, kind, resolveUrl) {
|
|
10303
|
-
const [state, setState] =
|
|
10304
|
-
const [attempt, setAttempt] =
|
|
10305
|
-
const resolveRef =
|
|
10306
|
-
|
|
10650
|
+
const [state, setState] = React51.useState({ status: "loading" });
|
|
10651
|
+
const [attempt, setAttempt] = React51.useState(0);
|
|
10652
|
+
const resolveRef = React51.useRef(resolveUrl);
|
|
10653
|
+
React51.useEffect(() => {
|
|
10307
10654
|
resolveRef.current = resolveUrl;
|
|
10308
10655
|
}, [resolveUrl]);
|
|
10309
|
-
|
|
10656
|
+
React51.useEffect(() => {
|
|
10310
10657
|
if (kind === "none") {
|
|
10311
10658
|
setState({ status: "ready" });
|
|
10312
10659
|
return;
|
|
@@ -10336,19 +10683,19 @@ function usePreview(file, kind, resolveUrl) {
|
|
|
10336
10683
|
if (ac) ac.abort();
|
|
10337
10684
|
};
|
|
10338
10685
|
}, [file.id, kind, attempt]);
|
|
10339
|
-
return { ...state, retry:
|
|
10686
|
+
return { ...state, retry: React51.useCallback(() => setAttempt((a) => a + 1), []) };
|
|
10340
10687
|
}
|
|
10341
10688
|
function PreviewBody({ file, kind, state }) {
|
|
10342
|
-
if (kind === "image") return /* @__PURE__ */
|
|
10343
|
-
if (kind === "pdf") return /* @__PURE__ */
|
|
10344
|
-
if (kind === "markdown") return /* @__PURE__ */
|
|
10689
|
+
if (kind === "image") return /* @__PURE__ */ jsx79("img", { className: "fd-fv-image", src: state.url, alt: file.name || "Preview" });
|
|
10690
|
+
if (kind === "pdf") return /* @__PURE__ */ jsx79("iframe", { className: "fd-fv-pdf", src: state.url, title: file.name || "PDF preview" });
|
|
10691
|
+
if (kind === "markdown") return /* @__PURE__ */ jsx79(Markdown, { className: "fd-fv-markdown", source: state.text || "" });
|
|
10345
10692
|
if (kind === "json") {
|
|
10346
10693
|
let pretty = state.text || "";
|
|
10347
10694
|
try {
|
|
10348
10695
|
pretty = JSON.stringify(JSON.parse(pretty), null, 2);
|
|
10349
10696
|
} catch {
|
|
10350
10697
|
}
|
|
10351
|
-
return /* @__PURE__ */
|
|
10698
|
+
return /* @__PURE__ */ jsx79(CodeBlock, { code: pretty, language: "json" });
|
|
10352
10699
|
}
|
|
10353
10700
|
if (kind === "csv") {
|
|
10354
10701
|
const tsv = extensionOf(file.name || "") === "tsv" || (file.mime || "").toLowerCase().includes("tab-separated");
|
|
@@ -10356,12 +10703,12 @@ function PreviewBody({ file, kind, state }) {
|
|
|
10356
10703
|
const head = rows[0] || [];
|
|
10357
10704
|
const dataRows = rows.slice(1);
|
|
10358
10705
|
const shown = dataRows.slice(0, FILE_VIEWER_CSV_ROW_LIMIT);
|
|
10359
|
-
return /* @__PURE__ */
|
|
10360
|
-
/* @__PURE__ */
|
|
10361
|
-
/* @__PURE__ */
|
|
10362
|
-
/* @__PURE__ */
|
|
10706
|
+
return /* @__PURE__ */ jsxs75("div", { className: "fd-fv-csv", children: [
|
|
10707
|
+
/* @__PURE__ */ jsxs75("table", { className: "fd-fv-csv-table", children: [
|
|
10708
|
+
/* @__PURE__ */ jsx79("thead", { children: /* @__PURE__ */ jsx79("tr", { children: head.map((c, i) => /* @__PURE__ */ jsx79("th", { children: c }, i)) }) }),
|
|
10709
|
+
/* @__PURE__ */ jsx79("tbody", { children: shown.map((r, ri) => /* @__PURE__ */ jsx79("tr", { children: head.map((_, ci) => /* @__PURE__ */ jsx79("td", { children: r[ci] ?? "" }, ci)) }, ri)) })
|
|
10363
10710
|
] }),
|
|
10364
|
-
dataRows.length > FILE_VIEWER_CSV_ROW_LIMIT ? /* @__PURE__ */
|
|
10711
|
+
dataRows.length > FILE_VIEWER_CSV_ROW_LIMIT ? /* @__PURE__ */ jsxs75("p", { className: "fd-fv-csv-note fd-body-sm fd-muted", children: [
|
|
10365
10712
|
"Showing the first ",
|
|
10366
10713
|
FILE_VIEWER_CSV_ROW_LIMIT.toLocaleString(),
|
|
10367
10714
|
" of ",
|
|
@@ -10370,24 +10717,24 @@ function PreviewBody({ file, kind, state }) {
|
|
|
10370
10717
|
] }) : null
|
|
10371
10718
|
] });
|
|
10372
10719
|
}
|
|
10373
|
-
if (kind === "text") return /* @__PURE__ */
|
|
10374
|
-
return /* @__PURE__ */
|
|
10375
|
-
/* @__PURE__ */
|
|
10376
|
-
/* @__PURE__ */
|
|
10377
|
-
/* @__PURE__ */
|
|
10720
|
+
if (kind === "text") return /* @__PURE__ */ jsx79(CodeBlock, { code: state.text || "", language: extensionOf(file.name || "") || void 0 });
|
|
10721
|
+
return /* @__PURE__ */ jsxs75("div", { className: "fd-fv-fallback", children: [
|
|
10722
|
+
/* @__PURE__ */ jsx79("span", { className: "fd-fv-fallback-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx79("i", { className: "ph ph-" + iconForMime(file.mime, file.name) }) }),
|
|
10723
|
+
/* @__PURE__ */ jsx79("span", { className: "fd-fv-fallback-title", children: "No preview available" }),
|
|
10724
|
+
/* @__PURE__ */ jsxs75("span", { className: "fd-fv-fallback-meta", children: [
|
|
10378
10725
|
file.mime || "Unknown type",
|
|
10379
10726
|
file.size ? " \xB7 " + formatBytes(file.size) : ""
|
|
10380
10727
|
] })
|
|
10381
10728
|
] });
|
|
10382
10729
|
}
|
|
10383
10730
|
function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", onClose, actions, className = "" }) {
|
|
10384
|
-
const kind =
|
|
10731
|
+
const kind = React51.useMemo(() => previewKindOf(file), [file]);
|
|
10385
10732
|
const preview = usePreview(file, kind, resolveUrl);
|
|
10386
|
-
const [tab, setTab] =
|
|
10387
|
-
const [downloading, setDownloading] =
|
|
10388
|
-
const [downloadError, setDownloadError] =
|
|
10733
|
+
const [tab, setTab] = React51.useState("preview");
|
|
10734
|
+
const [downloading, setDownloading] = React51.useState(false);
|
|
10735
|
+
const [downloadError, setDownloadError] = React51.useState(null);
|
|
10389
10736
|
const hasSide = sidePanel != null;
|
|
10390
|
-
|
|
10737
|
+
React51.useEffect(() => {
|
|
10391
10738
|
setTab("preview");
|
|
10392
10739
|
}, [file.id]);
|
|
10393
10740
|
const download = () => {
|
|
@@ -10403,29 +10750,29 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
|
|
|
10403
10750
|
a.remove();
|
|
10404
10751
|
}).catch((err) => setDownloadError(err && err.message || "Download failed.")).finally(() => setDownloading(false));
|
|
10405
10752
|
};
|
|
10406
|
-
return /* @__PURE__ */
|
|
10753
|
+
return /* @__PURE__ */ jsxs75(
|
|
10407
10754
|
"section",
|
|
10408
10755
|
{
|
|
10409
10756
|
className: ["fd-fv", hasSide && tab === "side" ? "is-tab-side" : "is-tab-preview", className].filter(Boolean).join(" "),
|
|
10410
10757
|
"aria-label": file.name || "File",
|
|
10411
10758
|
children: [
|
|
10412
|
-
/* @__PURE__ */
|
|
10413
|
-
/* @__PURE__ */
|
|
10414
|
-
/* @__PURE__ */
|
|
10415
|
-
/* @__PURE__ */
|
|
10416
|
-
/* @__PURE__ */
|
|
10759
|
+
/* @__PURE__ */ jsxs75("header", { className: "fd-fv-head", children: [
|
|
10760
|
+
/* @__PURE__ */ jsx79("span", { className: "fd-fv-head-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx79("i", { className: "ph ph-" + iconForMime(file.mime, file.name) }) }),
|
|
10761
|
+
/* @__PURE__ */ jsxs75("span", { className: "fd-fv-head-titles", children: [
|
|
10762
|
+
/* @__PURE__ */ jsx79("span", { className: "fd-fv-head-title", title: file.name, children: file.name }),
|
|
10763
|
+
/* @__PURE__ */ jsxs75("span", { className: "fd-fv-head-meta", children: [
|
|
10417
10764
|
file.mime || "Unknown type",
|
|
10418
10765
|
file.size ? " \xB7 " + formatBytes(file.size) : ""
|
|
10419
10766
|
] })
|
|
10420
10767
|
] }),
|
|
10421
|
-
/* @__PURE__ */
|
|
10768
|
+
/* @__PURE__ */ jsxs75("span", { className: "fd-fv-head-actions", children: [
|
|
10422
10769
|
actions,
|
|
10423
|
-
/* @__PURE__ */
|
|
10424
|
-
onClose ? /* @__PURE__ */
|
|
10770
|
+
/* @__PURE__ */ jsx79(Button, { variant: "secondary", size: "sm", icon: "download-simple", loading: downloading, onClick: download, children: "Download" }),
|
|
10771
|
+
onClose ? /* @__PURE__ */ jsx79(IconButton, { icon: "x", label: "Close", onClick: onClose }) : null
|
|
10425
10772
|
] })
|
|
10426
10773
|
] }),
|
|
10427
|
-
downloadError ? /* @__PURE__ */
|
|
10428
|
-
hasSide ? /* @__PURE__ */
|
|
10774
|
+
downloadError ? /* @__PURE__ */ jsx79("div", { className: "fd-fv-download-error", role: "alert", children: downloadError }) : null,
|
|
10775
|
+
hasSide ? /* @__PURE__ */ jsx79(
|
|
10429
10776
|
Tabs,
|
|
10430
10777
|
{
|
|
10431
10778
|
className: "fd-fv-tabs",
|
|
@@ -10434,19 +10781,19 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
|
|
|
10434
10781
|
onChange: (v) => setTab(v)
|
|
10435
10782
|
}
|
|
10436
10783
|
) : null,
|
|
10437
|
-
/* @__PURE__ */
|
|
10438
|
-
/* @__PURE__ */
|
|
10784
|
+
/* @__PURE__ */ jsxs75("div", { className: "fd-fv-body", children: [
|
|
10785
|
+
/* @__PURE__ */ jsx79("div", { className: "fd-fv-preview", role: "region", "aria-label": "Preview of " + (file.name || "file"), children: /* @__PURE__ */ jsx79(
|
|
10439
10786
|
LoadingRegion,
|
|
10440
10787
|
{
|
|
10441
10788
|
state: preview.status === "loading" ? "loading" : preview.status === "error" ? "error" : "ready",
|
|
10442
10789
|
error: preview.error,
|
|
10443
10790
|
label: "Loading preview of " + (file.name || "file"),
|
|
10444
10791
|
onRetry: preview.status === "error" ? preview.retry : void 0,
|
|
10445
|
-
skeleton: /* @__PURE__ */
|
|
10446
|
-
children: /* @__PURE__ */
|
|
10792
|
+
skeleton: /* @__PURE__ */ jsx79(Skeleton, { shape: "card", h: 220 }),
|
|
10793
|
+
children: /* @__PURE__ */ jsx79(PreviewBody, { file, kind, state: preview })
|
|
10447
10794
|
}
|
|
10448
10795
|
) }),
|
|
10449
|
-
hasSide ? /* @__PURE__ */
|
|
10796
|
+
hasSide ? /* @__PURE__ */ jsx79("div", { className: "fd-fv-side", role: "region", "aria-label": sidePanelLabel, children: sidePanel }) : null
|
|
10450
10797
|
] })
|
|
10451
10798
|
]
|
|
10452
10799
|
}
|
|
@@ -10454,8 +10801,8 @@ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", o
|
|
|
10454
10801
|
}
|
|
10455
10802
|
|
|
10456
10803
|
// src/components/files/FileBrowser.tsx
|
|
10457
|
-
import * as
|
|
10458
|
-
import { Fragment as
|
|
10804
|
+
import * as React52 from "react";
|
|
10805
|
+
import { Fragment as Fragment25, jsx as jsx80, jsxs as jsxs76 } from "react/jsx-runtime";
|
|
10459
10806
|
function childrenOf(folders, parentId) {
|
|
10460
10807
|
return folders.filter((f) => f.parentId === parentId).sort((a, b) => a.name.localeCompare(b.name));
|
|
10461
10808
|
}
|
|
@@ -10525,10 +10872,10 @@ function FolderPickerMenu({
|
|
|
10525
10872
|
destinations,
|
|
10526
10873
|
onPick
|
|
10527
10874
|
}) {
|
|
10528
|
-
const [open, setOpen] =
|
|
10875
|
+
const [open, setOpen] = React52.useState(false);
|
|
10529
10876
|
const zIndex = useOverlayLayer(open);
|
|
10530
|
-
const ref =
|
|
10531
|
-
|
|
10877
|
+
const ref = React52.useRef(null);
|
|
10878
|
+
React52.useEffect(() => {
|
|
10532
10879
|
if (!open) return;
|
|
10533
10880
|
const away = (e) => {
|
|
10534
10881
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
@@ -10543,8 +10890,8 @@ function FolderPickerMenu({
|
|
|
10543
10890
|
document.removeEventListener("keydown", esc);
|
|
10544
10891
|
};
|
|
10545
10892
|
}, [open]);
|
|
10546
|
-
return /* @__PURE__ */
|
|
10547
|
-
/* @__PURE__ */
|
|
10893
|
+
return /* @__PURE__ */ jsxs76("span", { className: "fd-fb-menu-anchor", ref, children: [
|
|
10894
|
+
/* @__PURE__ */ jsx80(
|
|
10548
10895
|
IconButton,
|
|
10549
10896
|
{
|
|
10550
10897
|
icon: "arrows-out-cardinal",
|
|
@@ -10556,9 +10903,9 @@ function FolderPickerMenu({
|
|
|
10556
10903
|
onClick: () => setOpen((v) => !v)
|
|
10557
10904
|
}
|
|
10558
10905
|
),
|
|
10559
|
-
open ? /* @__PURE__ */
|
|
10560
|
-
/* @__PURE__ */
|
|
10561
|
-
destinations.map((d) => /* @__PURE__ */
|
|
10906
|
+
open ? /* @__PURE__ */ jsx80(LayerProvider, { zIndex, children: /* @__PURE__ */ jsxs76("div", { className: "fd-fb-menu", role: "menu", "aria-label": label, style: { zIndex }, children: [
|
|
10907
|
+
/* @__PURE__ */ jsx80("span", { className: "fd-overline fd-muted fd-fb-menu-title", children: "Move to\u2026" }),
|
|
10908
|
+
destinations.map((d) => /* @__PURE__ */ jsxs76(
|
|
10562
10909
|
"button",
|
|
10563
10910
|
{
|
|
10564
10911
|
type: "button",
|
|
@@ -10570,7 +10917,7 @@ function FolderPickerMenu({
|
|
|
10570
10917
|
setOpen(false);
|
|
10571
10918
|
},
|
|
10572
10919
|
children: [
|
|
10573
|
-
/* @__PURE__ */
|
|
10920
|
+
/* @__PURE__ */ jsx80("i", { className: "ph ph-" + (d.id == null ? "house-simple" : "folder-simple"), "aria-hidden": "true" }),
|
|
10574
10921
|
d.name
|
|
10575
10922
|
]
|
|
10576
10923
|
},
|
|
@@ -10580,8 +10927,8 @@ function FolderPickerMenu({
|
|
|
10580
10927
|
] });
|
|
10581
10928
|
}
|
|
10582
10929
|
function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropPayload, actions, className = "" }) {
|
|
10583
|
-
const [over, setOver] =
|
|
10584
|
-
const dragCounter =
|
|
10930
|
+
const [over, setOver] = React52.useState(false);
|
|
10931
|
+
const dragCounter = React52.useRef(0);
|
|
10585
10932
|
const dropHandlers = isDropTarget ? {
|
|
10586
10933
|
onDragEnter: (e) => {
|
|
10587
10934
|
if (!hasInternalPayload(e)) return;
|
|
@@ -10606,8 +10953,8 @@ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropP
|
|
|
10606
10953
|
if (payload && onDropPayload) onDropPayload(payload);
|
|
10607
10954
|
}
|
|
10608
10955
|
} : {};
|
|
10609
|
-
return /* @__PURE__ */
|
|
10610
|
-
/* @__PURE__ */
|
|
10956
|
+
return /* @__PURE__ */ jsxs76("div", { className: ["fd-fb-row", over ? "is-drop-target" : "", className].filter(Boolean).join(" "), ...dropHandlers, children: [
|
|
10957
|
+
/* @__PURE__ */ jsxs76(
|
|
10611
10958
|
"button",
|
|
10612
10959
|
{
|
|
10613
10960
|
type: "button",
|
|
@@ -10616,26 +10963,26 @@ function RowShell({ icon, name, meta, onOpen, draggableId, isDropTarget, onDropP
|
|
|
10616
10963
|
draggable: !!draggableId,
|
|
10617
10964
|
onDragStart: draggableId ? (e) => writeDragPayload(e, draggableId) : void 0,
|
|
10618
10965
|
children: [
|
|
10619
|
-
/* @__PURE__ */
|
|
10620
|
-
/* @__PURE__ */
|
|
10621
|
-
/* @__PURE__ */
|
|
10622
|
-
meta ? /* @__PURE__ */
|
|
10966
|
+
/* @__PURE__ */ jsx80("span", { className: "fd-fb-row-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx80("i", { className: "ph ph-" + icon }) }),
|
|
10967
|
+
/* @__PURE__ */ jsxs76("span", { className: "fd-fb-row-text", children: [
|
|
10968
|
+
/* @__PURE__ */ jsx80("span", { className: "fd-fb-row-name", title: name, children: name }),
|
|
10969
|
+
meta ? /* @__PURE__ */ jsx80("span", { className: "fd-fb-row-meta", children: meta }) : null
|
|
10623
10970
|
] })
|
|
10624
10971
|
]
|
|
10625
10972
|
}
|
|
10626
10973
|
),
|
|
10627
|
-
actions ? /* @__PURE__ */
|
|
10974
|
+
actions ? /* @__PURE__ */ jsx80("span", { className: "fd-fb-row-actions", children: actions }) : null
|
|
10628
10975
|
] });
|
|
10629
10976
|
}
|
|
10630
10977
|
function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveInto, canMove }) {
|
|
10631
10978
|
const kids = childrenOf(folders, folder.id);
|
|
10632
|
-
const [expanded, setExpanded] =
|
|
10633
|
-
|
|
10979
|
+
const [expanded, setExpanded] = React52.useState(() => currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId));
|
|
10980
|
+
React52.useEffect(() => {
|
|
10634
10981
|
if (currentFolderId != null && isSelfOrDescendant(folders, folder.id, currentFolderId)) setExpanded(true);
|
|
10635
10982
|
}, [currentFolderId, folder.id, folders]);
|
|
10636
10983
|
const isCurrent = folder.id === currentFolderId;
|
|
10637
|
-
return /* @__PURE__ */
|
|
10638
|
-
/* @__PURE__ */
|
|
10984
|
+
return /* @__PURE__ */ jsxs76("div", { className: "fd-fb-tree-node", children: [
|
|
10985
|
+
/* @__PURE__ */ jsx80(
|
|
10639
10986
|
RowShell,
|
|
10640
10987
|
{
|
|
10641
10988
|
icon: folder.id === currentFolderId ? "folder-open" : "folder-simple",
|
|
@@ -10645,7 +10992,7 @@ function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveI
|
|
|
10645
10992
|
isDropTarget: canMove,
|
|
10646
10993
|
onDropPayload: (payload) => onMoveInto(payload, folder.id),
|
|
10647
10994
|
className: ["fd-fb-tree-row", isCurrent ? "is-current" : ""].filter(Boolean).join(" "),
|
|
10648
|
-
actions: kids.length ? /* @__PURE__ */
|
|
10995
|
+
actions: kids.length ? /* @__PURE__ */ jsx80(
|
|
10649
10996
|
IconButton,
|
|
10650
10997
|
{
|
|
10651
10998
|
icon: expanded ? "caret-down" : "caret-right",
|
|
@@ -10657,7 +11004,7 @@ function TreeNode({ folder, depth, folders, currentFolderId, onNavigate, onMoveI
|
|
|
10657
11004
|
) : void 0
|
|
10658
11005
|
}
|
|
10659
11006
|
),
|
|
10660
|
-
expanded && kids.length ? /* @__PURE__ */
|
|
11007
|
+
expanded && kids.length ? /* @__PURE__ */ jsx80("div", { className: "fd-fb-tree-children", style: { paddingLeft: 14 }, children: kids.map((k) => /* @__PURE__ */ jsx80(
|
|
10661
11008
|
TreeNode,
|
|
10662
11009
|
{
|
|
10663
11010
|
folder: k,
|
|
@@ -10696,15 +11043,15 @@ function FileBrowser({
|
|
|
10696
11043
|
const canUpload = perm.canUpload !== false && !!onUpload;
|
|
10697
11044
|
const canRemoveFile = perm.canRemoveFile !== false && !!onRemoveFile;
|
|
10698
11045
|
const canDeleteFolder = perm.canDeleteFolder !== false && !!onDeleteFolder;
|
|
10699
|
-
const [createOpen, setCreateOpen] =
|
|
10700
|
-
const [createName, setCreateName] =
|
|
10701
|
-
const [renameTarget, setRenameTarget] =
|
|
10702
|
-
const [renameValue, setRenameValue] =
|
|
10703
|
-
const [deleteTarget, setDeleteTarget] =
|
|
10704
|
-
const uploadInputRef =
|
|
10705
|
-
const crumbFolders =
|
|
10706
|
-
const subfolders =
|
|
10707
|
-
const currentFiles =
|
|
11046
|
+
const [createOpen, setCreateOpen] = React52.useState(false);
|
|
11047
|
+
const [createName, setCreateName] = React52.useState("");
|
|
11048
|
+
const [renameTarget, setRenameTarget] = React52.useState(null);
|
|
11049
|
+
const [renameValue, setRenameValue] = React52.useState("");
|
|
11050
|
+
const [deleteTarget, setDeleteTarget] = React52.useState(null);
|
|
11051
|
+
const uploadInputRef = React52.useRef(null);
|
|
11052
|
+
const crumbFolders = React52.useMemo(() => ancestorsOf(folders, currentFolderId), [folders, currentFolderId]);
|
|
11053
|
+
const subfolders = React52.useMemo(() => childrenOf(folders, currentFolderId), [folders, currentFolderId]);
|
|
11054
|
+
const currentFiles = React52.useMemo(
|
|
10708
11055
|
() => files.filter((f) => f.folderId === currentFolderId).sort((a, b) => a.name.localeCompare(b.name)),
|
|
10709
11056
|
[files, currentFolderId]
|
|
10710
11057
|
);
|
|
@@ -10743,10 +11090,10 @@ function FileBrowser({
|
|
|
10743
11090
|
if (deleteTarget && onDeleteFolder) onDeleteFolder(deleteTarget);
|
|
10744
11091
|
setDeleteTarget(null);
|
|
10745
11092
|
};
|
|
10746
|
-
const content = /* @__PURE__ */
|
|
10747
|
-
/* @__PURE__ */
|
|
10748
|
-
/* @__PURE__ */
|
|
10749
|
-
/* @__PURE__ */
|
|
11093
|
+
const content = /* @__PURE__ */ jsxs76("div", { className: ["fd-fb", className].filter(Boolean).join(" "), children: [
|
|
11094
|
+
/* @__PURE__ */ jsxs76("div", { className: "fd-fb-head", children: [
|
|
11095
|
+
/* @__PURE__ */ jsxs76("nav", { className: "fd-fb-crumbs", "aria-label": "Folder path", children: [
|
|
11096
|
+
/* @__PURE__ */ jsx80(
|
|
10750
11097
|
BreadcrumbCrumb,
|
|
10751
11098
|
{
|
|
10752
11099
|
label: "Home",
|
|
@@ -10756,7 +11103,7 @@ function FileBrowser({
|
|
|
10756
11103
|
onDropPayload: (p) => moveIntoFolder(p, null)
|
|
10757
11104
|
}
|
|
10758
11105
|
),
|
|
10759
|
-
crumbFolders.map((f, i) => /* @__PURE__ */
|
|
11106
|
+
crumbFolders.map((f, i) => /* @__PURE__ */ jsx80(
|
|
10760
11107
|
BreadcrumbCrumb,
|
|
10761
11108
|
{
|
|
10762
11109
|
label: f.name,
|
|
@@ -10768,11 +11115,11 @@ function FileBrowser({
|
|
|
10768
11115
|
f.id
|
|
10769
11116
|
))
|
|
10770
11117
|
] }),
|
|
10771
|
-
/* @__PURE__ */
|
|
10772
|
-
canCreateFolder ? /* @__PURE__ */
|
|
10773
|
-
canUpload ? /* @__PURE__ */
|
|
10774
|
-
/* @__PURE__ */
|
|
10775
|
-
/* @__PURE__ */
|
|
11118
|
+
/* @__PURE__ */ jsxs76("span", { className: "fd-fb-actions", children: [
|
|
11119
|
+
canCreateFolder ? /* @__PURE__ */ jsx80(Button, { variant: "secondary", size: "sm", icon: "folder-simple-plus", onClick: openCreate, children: "New folder" }) : null,
|
|
11120
|
+
canUpload ? /* @__PURE__ */ jsxs76(Fragment25, { children: [
|
|
11121
|
+
/* @__PURE__ */ jsx80(Button, { variant: "secondary", size: "sm", icon: "upload-simple", onClick: () => uploadInputRef.current && uploadInputRef.current.click(), children: "Upload" }),
|
|
11122
|
+
/* @__PURE__ */ jsx80(
|
|
10776
11123
|
"input",
|
|
10777
11124
|
{
|
|
10778
11125
|
ref: uploadInputRef,
|
|
@@ -10790,9 +11137,9 @@ function FileBrowser({
|
|
|
10790
11137
|
] }) : null
|
|
10791
11138
|
] })
|
|
10792
11139
|
] }),
|
|
10793
|
-
/* @__PURE__ */
|
|
10794
|
-
/* @__PURE__ */
|
|
10795
|
-
/* @__PURE__ */
|
|
11140
|
+
/* @__PURE__ */ jsxs76("div", { className: "fd-fb-body", children: [
|
|
11141
|
+
/* @__PURE__ */ jsxs76("div", { className: "fd-fb-tree", role: "region", "aria-label": "Folder tree", children: [
|
|
11142
|
+
/* @__PURE__ */ jsx80(
|
|
10796
11143
|
RowShell,
|
|
10797
11144
|
{
|
|
10798
11145
|
icon: "house-simple",
|
|
@@ -10803,7 +11150,7 @@ function FileBrowser({
|
|
|
10803
11150
|
className: ["fd-fb-tree-row", currentFolderId == null ? "is-current" : ""].filter(Boolean).join(" ")
|
|
10804
11151
|
}
|
|
10805
11152
|
),
|
|
10806
|
-
childrenOf(folders, null).map((f) => /* @__PURE__ */
|
|
11153
|
+
childrenOf(folders, null).map((f) => /* @__PURE__ */ jsx80(
|
|
10807
11154
|
TreeNode,
|
|
10808
11155
|
{
|
|
10809
11156
|
folder: f,
|
|
@@ -10817,18 +11164,18 @@ function FileBrowser({
|
|
|
10817
11164
|
f.id
|
|
10818
11165
|
))
|
|
10819
11166
|
] }),
|
|
10820
|
-
/* @__PURE__ */
|
|
11167
|
+
/* @__PURE__ */ jsx80("div", { className: "fd-fb-main", role: "region", "aria-label": "Folder contents", children: /* @__PURE__ */ jsx80(
|
|
10821
11168
|
LoadingRegion,
|
|
10822
11169
|
{
|
|
10823
11170
|
state: loading ? "loading" : "ready",
|
|
10824
11171
|
label: "Loading folder contents",
|
|
10825
|
-
skeleton: /* @__PURE__ */
|
|
10826
|
-
/* @__PURE__ */
|
|
10827
|
-
/* @__PURE__ */
|
|
10828
|
-
/* @__PURE__ */
|
|
11172
|
+
skeleton: /* @__PURE__ */ jsxs76("div", { className: "fd-stack", style: { gap: 8, padding: 12 }, children: [
|
|
11173
|
+
/* @__PURE__ */ jsx80(Skeleton, { h: 44, radius: "10px" }),
|
|
11174
|
+
/* @__PURE__ */ jsx80(Skeleton, { h: 44, radius: "10px" }),
|
|
11175
|
+
/* @__PURE__ */ jsx80(Skeleton, { h: 44, radius: "10px" })
|
|
10829
11176
|
] }),
|
|
10830
|
-
children: isEmpty ? /* @__PURE__ */
|
|
10831
|
-
subfolders.map((f) => /* @__PURE__ */
|
|
11177
|
+
children: isEmpty ? /* @__PURE__ */ jsx80(EmptyState, { icon: "folder-open", title: "This folder is empty", children: canUpload ? "Drop files here, or use Upload above." : "Nothing has been filed here yet." }) : /* @__PURE__ */ jsxs76("div", { className: "fd-fb-list", children: [
|
|
11178
|
+
subfolders.map((f) => /* @__PURE__ */ jsx80(
|
|
10832
11179
|
RowShell,
|
|
10833
11180
|
{
|
|
10834
11181
|
icon: "folder-simple",
|
|
@@ -10837,9 +11184,9 @@ function FileBrowser({
|
|
|
10837
11184
|
draggableId: canMove ? { kind: "folder", id: f.id } : void 0,
|
|
10838
11185
|
isDropTarget: canMove,
|
|
10839
11186
|
onDropPayload: (p) => moveIntoFolder(p, f.id),
|
|
10840
|
-
actions: /* @__PURE__ */
|
|
10841
|
-
canRenameFolder ? /* @__PURE__ */
|
|
10842
|
-
canMove ? /* @__PURE__ */
|
|
11187
|
+
actions: /* @__PURE__ */ jsxs76(Fragment25, { children: [
|
|
11188
|
+
canRenameFolder ? /* @__PURE__ */ jsx80(IconButton, { icon: "pencil-simple", label: "Rename " + f.name, size: "sm", variant: "ghost", onClick: () => openRename(f) }) : null,
|
|
11189
|
+
canMove ? /* @__PURE__ */ jsx80(
|
|
10843
11190
|
FolderPickerMenu,
|
|
10844
11191
|
{
|
|
10845
11192
|
label: "Move " + f.name,
|
|
@@ -10847,12 +11194,12 @@ function FileBrowser({
|
|
|
10847
11194
|
onPick: (target) => moveIntoFolder({ kind: "folder", id: f.id }, target)
|
|
10848
11195
|
}
|
|
10849
11196
|
) : null,
|
|
10850
|
-
canDeleteFolder ? /* @__PURE__ */
|
|
11197
|
+
canDeleteFolder ? /* @__PURE__ */ jsx80(IconButton, { icon: "trash-simple", label: "Delete " + f.name, size: "sm", variant: "ghost", onClick: () => openDelete(f) }) : null
|
|
10851
11198
|
] })
|
|
10852
11199
|
},
|
|
10853
11200
|
f.id
|
|
10854
11201
|
)),
|
|
10855
|
-
currentFiles.map((file) => /* @__PURE__ */
|
|
11202
|
+
currentFiles.map((file) => /* @__PURE__ */ jsx80(
|
|
10856
11203
|
RowShell,
|
|
10857
11204
|
{
|
|
10858
11205
|
icon: iconForMime(file.mime, file.name),
|
|
@@ -10860,8 +11207,8 @@ function FileBrowser({
|
|
|
10860
11207
|
meta: file.size ? formatBytes(file.size) : void 0,
|
|
10861
11208
|
onOpen: onOpenFile ? () => onOpenFile(file) : void 0,
|
|
10862
11209
|
draggableId: canMove ? { kind: "file", id: file.id } : void 0,
|
|
10863
|
-
actions: /* @__PURE__ */
|
|
10864
|
-
canMove ? /* @__PURE__ */
|
|
11210
|
+
actions: /* @__PURE__ */ jsxs76(Fragment25, { children: [
|
|
11211
|
+
canMove ? /* @__PURE__ */ jsx80(
|
|
10865
11212
|
FolderPickerMenu,
|
|
10866
11213
|
{
|
|
10867
11214
|
label: "Move " + file.name,
|
|
@@ -10869,7 +11216,7 @@ function FileBrowser({
|
|
|
10869
11216
|
onPick: (target) => onMoveFile && onMoveFile(file.id, target)
|
|
10870
11217
|
}
|
|
10871
11218
|
) : null,
|
|
10872
|
-
canRemoveFile ? /* @__PURE__ */
|
|
11219
|
+
canRemoveFile ? /* @__PURE__ */ jsx80(IconButton, { icon: "trash-simple", label: "Remove " + file.name, size: "sm", variant: "ghost", onClick: () => onRemoveFile && onRemoveFile(file) }) : null
|
|
10873
11220
|
] })
|
|
10874
11221
|
},
|
|
10875
11222
|
file.id
|
|
@@ -10878,10 +11225,10 @@ function FileBrowser({
|
|
|
10878
11225
|
}
|
|
10879
11226
|
) })
|
|
10880
11227
|
] }),
|
|
10881
|
-
/* @__PURE__ */
|
|
10882
|
-
/* @__PURE__ */
|
|
10883
|
-
/* @__PURE__ */
|
|
10884
|
-
] }), children: /* @__PURE__ */
|
|
11228
|
+
/* @__PURE__ */ jsx80(Modal, { open: createOpen, title: "New folder", onClose: () => setCreateOpen(false), footer: /* @__PURE__ */ jsxs76(Fragment25, { children: [
|
|
11229
|
+
/* @__PURE__ */ jsx80(Button, { variant: "secondary", onClick: () => setCreateOpen(false), children: "Cancel" }),
|
|
11230
|
+
/* @__PURE__ */ jsx80(Button, { onClick: submitCreate, disabled: !createName.trim(), children: "Create" })
|
|
11231
|
+
] }), children: /* @__PURE__ */ jsx80(
|
|
10885
11232
|
"input",
|
|
10886
11233
|
{
|
|
10887
11234
|
className: "fd-fb-input",
|
|
@@ -10895,10 +11242,10 @@ function FileBrowser({
|
|
|
10895
11242
|
autoFocus: true
|
|
10896
11243
|
}
|
|
10897
11244
|
) }),
|
|
10898
|
-
/* @__PURE__ */
|
|
10899
|
-
/* @__PURE__ */
|
|
10900
|
-
/* @__PURE__ */
|
|
10901
|
-
] }), children: /* @__PURE__ */
|
|
11245
|
+
/* @__PURE__ */ jsx80(Modal, { open: renameTarget != null, title: "Rename folder", onClose: () => setRenameTarget(null), footer: /* @__PURE__ */ jsxs76(Fragment25, { children: [
|
|
11246
|
+
/* @__PURE__ */ jsx80(Button, { variant: "secondary", onClick: () => setRenameTarget(null), children: "Cancel" }),
|
|
11247
|
+
/* @__PURE__ */ jsx80(Button, { onClick: submitRename, disabled: !renameValue.trim(), children: "Save" })
|
|
11248
|
+
] }), children: /* @__PURE__ */ jsx80(
|
|
10902
11249
|
"input",
|
|
10903
11250
|
{
|
|
10904
11251
|
className: "fd-fb-input",
|
|
@@ -10912,22 +11259,22 @@ function FileBrowser({
|
|
|
10912
11259
|
autoFocus: true
|
|
10913
11260
|
}
|
|
10914
11261
|
) }),
|
|
10915
|
-
/* @__PURE__ */
|
|
11262
|
+
/* @__PURE__ */ jsx80(
|
|
10916
11263
|
Modal,
|
|
10917
11264
|
{
|
|
10918
11265
|
open: deleteTarget != null,
|
|
10919
11266
|
title: deleteTarget ? "Delete \u201C" + deleteTarget.name + "\u201D?" : "Delete folder?",
|
|
10920
11267
|
description: "This can't be undone. The folder must be empty.",
|
|
10921
11268
|
onClose: () => setDeleteTarget(null),
|
|
10922
|
-
footer: /* @__PURE__ */
|
|
10923
|
-
/* @__PURE__ */
|
|
10924
|
-
/* @__PURE__ */
|
|
11269
|
+
footer: /* @__PURE__ */ jsxs76(Fragment25, { children: [
|
|
11270
|
+
/* @__PURE__ */ jsx80(Button, { variant: "secondary", onClick: () => setDeleteTarget(null), children: "Cancel" }),
|
|
11271
|
+
/* @__PURE__ */ jsx80(Button, { variant: "danger", onClick: confirmDelete, children: "Delete" })
|
|
10925
11272
|
] })
|
|
10926
11273
|
}
|
|
10927
11274
|
)
|
|
10928
11275
|
] });
|
|
10929
11276
|
if (!canUpload) return content;
|
|
10930
|
-
return /* @__PURE__ */
|
|
11277
|
+
return /* @__PURE__ */ jsx80(Dropzone, { className: "fd-fb-dropzone", onFiles: (picked) => onUpload && onUpload(picked, currentFolderId), label: "Drop files to upload", children: content });
|
|
10931
11278
|
}
|
|
10932
11279
|
function BreadcrumbCrumb({
|
|
10933
11280
|
label,
|
|
@@ -10936,8 +11283,8 @@ function BreadcrumbCrumb({
|
|
|
10936
11283
|
isDropTarget,
|
|
10937
11284
|
onDropPayload
|
|
10938
11285
|
}) {
|
|
10939
|
-
const [over, setOver] =
|
|
10940
|
-
const dragCounter =
|
|
11286
|
+
const [over, setOver] = React52.useState(false);
|
|
11287
|
+
const dragCounter = React52.useRef(0);
|
|
10941
11288
|
const dropHandlers = isDropTarget ? {
|
|
10942
11289
|
onDragEnter: (e) => {
|
|
10943
11290
|
if (!hasInternalPayload(e)) return;
|
|
@@ -10962,11 +11309,11 @@ function BreadcrumbCrumb({
|
|
|
10962
11309
|
if (payload && onDropPayload) onDropPayload(payload);
|
|
10963
11310
|
}
|
|
10964
11311
|
} : {};
|
|
10965
|
-
return /* @__PURE__ */
|
|
11312
|
+
return /* @__PURE__ */ jsx80("span", { className: ["fd-fb-crumb", over ? "is-drop-target" : ""].filter(Boolean).join(" "), ...dropHandlers, children: isCurrent ? /* @__PURE__ */ jsx80("span", { className: "fd-fb-crumb-current", "aria-current": "page", children: label }) : /* @__PURE__ */ jsx80("button", { type: "button", className: "fd-fb-crumb-link", onClick, children: label }) });
|
|
10966
11313
|
}
|
|
10967
11314
|
|
|
10968
11315
|
// src/kits/query.ts
|
|
10969
|
-
import * as
|
|
11316
|
+
import * as React53 from "react";
|
|
10970
11317
|
function eqFilter(get2) {
|
|
10971
11318
|
return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
|
|
10972
11319
|
}
|
|
@@ -10998,22 +11345,22 @@ function compare(a, b, dir) {
|
|
|
10998
11345
|
var API = null;
|
|
10999
11346
|
var PREFS = null;
|
|
11000
11347
|
function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
|
|
11001
|
-
const [query, setQuery] =
|
|
11348
|
+
const [query, setQuery] = React53.useState(() => {
|
|
11002
11349
|
const store = PREFS || window.PlannerPrefs;
|
|
11003
11350
|
const saved = prefsKey && store ? store.getTable(prefsKey) : {};
|
|
11004
11351
|
return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
|
|
11005
11352
|
});
|
|
11006
|
-
const savePref =
|
|
11353
|
+
const savePref = React53.useCallback((patch2) => {
|
|
11007
11354
|
const store = PREFS || window.PlannerPrefs;
|
|
11008
11355
|
if (prefsKey && store) store.setTable(prefsKey, patch2);
|
|
11009
11356
|
}, [prefsKey]);
|
|
11010
|
-
const [res, setRes] =
|
|
11011
|
-
const [loading, setLoading] =
|
|
11012
|
-
const seq2 =
|
|
11357
|
+
const [res, setRes] = React53.useState(null);
|
|
11358
|
+
const [loading, setLoading] = React53.useState(true);
|
|
11359
|
+
const seq2 = React53.useRef(0);
|
|
11013
11360
|
const depKey = (deps || []).join("|");
|
|
11014
11361
|
const paramKey = JSON.stringify(params || {});
|
|
11015
11362
|
const queryKey = JSON.stringify(query);
|
|
11016
|
-
|
|
11363
|
+
React53.useEffect(() => {
|
|
11017
11364
|
const id = ++seq2.current;
|
|
11018
11365
|
setLoading(true);
|
|
11019
11366
|
const t = setTimeout(() => {
|
|
@@ -11314,6 +11661,8 @@ export {
|
|
|
11314
11661
|
AccountMenu,
|
|
11315
11662
|
AgentChatPanel,
|
|
11316
11663
|
ApiSpecBrowser,
|
|
11664
|
+
AppSwitcher,
|
|
11665
|
+
AppearanceMenu,
|
|
11317
11666
|
Avatar,
|
|
11318
11667
|
AvatarStack,
|
|
11319
11668
|
Badge,
|
|
@@ -11344,8 +11693,11 @@ export {
|
|
|
11344
11693
|
CsiBadge,
|
|
11345
11694
|
CsiHero,
|
|
11346
11695
|
CsiMeter,
|
|
11696
|
+
DEFAULT_PREFS,
|
|
11697
|
+
DEFAULT_STORAGE_KEY,
|
|
11347
11698
|
DataTable,
|
|
11348
11699
|
DatePicker,
|
|
11700
|
+
DemoSessionProvider,
|
|
11349
11701
|
DiffBlock,
|
|
11350
11702
|
Drawer,
|
|
11351
11703
|
Dropzone,
|
|
@@ -11415,6 +11767,7 @@ export {
|
|
|
11415
11767
|
SegmentedMeter,
|
|
11416
11768
|
Select,
|
|
11417
11769
|
SessionKit,
|
|
11770
|
+
SessionProvider,
|
|
11418
11771
|
SidebarNav,
|
|
11419
11772
|
Skeleton,
|
|
11420
11773
|
SkeletonText,
|
|
@@ -11443,9 +11796,11 @@ export {
|
|
|
11443
11796
|
VirtualList,
|
|
11444
11797
|
acceptMatches,
|
|
11445
11798
|
anyOfFilter,
|
|
11799
|
+
applyPrefs,
|
|
11446
11800
|
channelWeightOf,
|
|
11447
11801
|
computeNeedMore,
|
|
11448
11802
|
computeVirtualWindow,
|
|
11803
|
+
createLocalStorageAppearanceStore,
|
|
11449
11804
|
createVersionStore,
|
|
11450
11805
|
eqFilter,
|
|
11451
11806
|
extensionOf,
|
|
@@ -11466,6 +11821,7 @@ export {
|
|
|
11466
11821
|
isSystemMessage,
|
|
11467
11822
|
jobBucket,
|
|
11468
11823
|
languageLabel,
|
|
11824
|
+
loadPrefs,
|
|
11469
11825
|
markdownToText,
|
|
11470
11826
|
meterFormats,
|
|
11471
11827
|
modifierLabel,
|
|
@@ -11475,14 +11831,18 @@ export {
|
|
|
11475
11831
|
preferenceScore,
|
|
11476
11832
|
preferenceSegments,
|
|
11477
11833
|
rangeFilter,
|
|
11834
|
+
resolveTheme,
|
|
11478
11835
|
revokeAttachment,
|
|
11836
|
+
savePrefs,
|
|
11479
11837
|
toAttachment,
|
|
11480
11838
|
tokenize,
|
|
11839
|
+
useAppearancePrefs,
|
|
11481
11840
|
useChatEngine,
|
|
11482
11841
|
useFeatureStatus,
|
|
11483
11842
|
usePopoverPosition,
|
|
11484
11843
|
useRuntimeMode,
|
|
11485
11844
|
useServerTable,
|
|
11845
|
+
useSessionContext,
|
|
11486
11846
|
useStagedFiles,
|
|
11487
11847
|
visibleMessages
|
|
11488
11848
|
};
|