@arbidocs/blocks 0.3.116 → 0.3.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -604,7 +604,25 @@ function ConnectionProvider({
604
604
  await arbi.selectWorkspace(target.external_id);
605
605
  setWorkspaceId(target.external_id);
606
606
  }
607
- setUser({ name: email.split("@")[0], email });
607
+ let resolved = { name: email.split("@")[0], email };
608
+ try {
609
+ const me = arbi.user.identity();
610
+ const members = await arbi.workspaces.listUsers();
611
+ const self = members.find((m) => m.user.external_id === me?.externalId)?.user;
612
+ if (self) {
613
+ const fullName = [self.given_name, self.family_name].filter(Boolean).join(" ").trim();
614
+ resolved = {
615
+ name: fullName || email.split("@")[0],
616
+ email: self.email || email,
617
+ externalId: self.external_id,
618
+ picture: self.picture ?? null
619
+ };
620
+ } else if (me?.externalId) {
621
+ resolved.externalId = me.externalId;
622
+ }
623
+ } catch {
624
+ }
625
+ setUser(resolved);
608
626
  setStatus("authenticated");
609
627
  } catch (e) {
610
628
  setError(e instanceof Error ? e.message : "Sign-in failed. Check your credentials.");
@@ -2450,6 +2468,74 @@ function AppShell({
2450
2468
  }
2451
2469
  );
2452
2470
  }
2471
+ function AppHeader({
2472
+ onSearch,
2473
+ onAsk,
2474
+ searchPlaceholder = "Ask AI or search\u2026",
2475
+ searchKbd = "\u2318K",
2476
+ askLabel = "Ask AI",
2477
+ leading,
2478
+ actions,
2479
+ user,
2480
+ userItems,
2481
+ onLogout,
2482
+ onNotificationNavigate,
2483
+ notificationTones,
2484
+ classNames,
2485
+ testIdPrefix = "app"
2486
+ }) {
2487
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2488
+ /* @__PURE__ */ jsxRuntime.jsxs(
2489
+ "button",
2490
+ {
2491
+ type: "button",
2492
+ onClick: onSearch,
2493
+ className: classNames?.search ?? "relative hidden max-w-md flex-1 items-center rounded-md border border-input bg-background py-2 pl-9 pr-3 text-left text-sm text-muted-foreground transition-colors hover:bg-accent md:flex",
2494
+ "data-testid": tid(testIdPrefix, "global-search"),
2495
+ children: [
2496
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
2497
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: searchPlaceholder }),
2498
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "rounded border border-border px-1.5 py-0.5 text-[10px]", children: searchKbd })
2499
+ ]
2500
+ }
2501
+ ),
2502
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex items-center gap-3", children: [
2503
+ leading,
2504
+ /* @__PURE__ */ jsxRuntime.jsxs(
2505
+ react.Button,
2506
+ {
2507
+ size: "sm",
2508
+ onClick: onAsk ?? onSearch,
2509
+ className: classNames?.askButton,
2510
+ "data-testid": tid(testIdPrefix, "ask-ai"),
2511
+ children: [
2512
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "size-4" }),
2513
+ askLabel
2514
+ ]
2515
+ }
2516
+ ),
2517
+ actions,
2518
+ /* @__PURE__ */ jsxRuntime.jsx(
2519
+ react.NotificationBell,
2520
+ {
2521
+ testId: tid(testIdPrefix, "notifications"),
2522
+ onNavigate: onNotificationNavigate,
2523
+ toneClassNames: notificationTones
2524
+ }
2525
+ ),
2526
+ /* @__PURE__ */ jsxRuntime.jsx(
2527
+ react.UserMenu,
2528
+ {
2529
+ user,
2530
+ items: userItems,
2531
+ onLogout,
2532
+ testId: tid(testIdPrefix, "user-menu"),
2533
+ logoutTestId: tid(testIdPrefix, "logout")
2534
+ }
2535
+ )
2536
+ ] })
2537
+ ] });
2538
+ }
2453
2539
  var LiveDataContext = react$1.createContext(false);
2454
2540
  function useLiveDataActive() {
2455
2541
  return react$1.useContext(LiveDataContext);
@@ -3219,6 +3305,9 @@ function useImageGen() {
3219
3305
  reset: task.reset
3220
3306
  };
3221
3307
  }
3308
+ function toJpgName(name) {
3309
+ return name.replace(/\.[^.]+$/, "") + ".jpg";
3310
+ }
3222
3311
  var IMAGE_EXT = /\.(png|jpe?g|webp|gif|avif|svg)$/i;
3223
3312
  function ImageFieldControl({
3224
3313
  value,
@@ -3232,9 +3321,12 @@ function ImageFieldControl({
3232
3321
  const [url, setUrl] = react$1.useState("");
3233
3322
  const [prompt, setPrompt] = react$1.useState("");
3234
3323
  const [busy, setBusy] = react$1.useState(false);
3324
+ const [cropSrc, setCropSrc] = react$1.useState(null);
3325
+ const [cropName, setCropName] = react$1.useState("image.jpg");
3235
3326
  const arbi = react.useArbi();
3236
3327
  const gen = useImageGen();
3237
3328
  const queryClient = reactQuery.useQueryClient();
3329
+ const currentAssetImage = useAssetImage(isAssetRef(value) ? assetRefId(value) : "");
3238
3330
  const tp = (...q) => tid(`${testIdPrefix}-image`, name, ...q);
3239
3331
  const refreshAssets = () => queryClient.invalidateQueries({ queryKey: ["arbi", "documents", workspaceId] });
3240
3332
  const { data: docs } = react.useDocuments(workspaceId);
@@ -3249,13 +3341,24 @@ function ImageFieldControl({
3249
3341
  [imageDocs]
3250
3342
  );
3251
3343
  const { data: thumbs } = react.useThumbnails(imageIds);
3252
- const onUpload = async (e) => {
3344
+ const onUpload = (e) => {
3253
3345
  const file = e.target.files?.[0];
3254
3346
  if (!file) return;
3347
+ setCropName(toJpgName(file.name));
3348
+ setCropSrc(URL.createObjectURL(file));
3349
+ e.target.value = "";
3350
+ };
3351
+ const openReframe = () => {
3352
+ if (!currentAssetImage) return;
3353
+ setCropName("reframe.jpg");
3354
+ setCropSrc(currentAssetImage);
3355
+ };
3356
+ const uploadBlobAsAsset = async (blob, name2) => {
3255
3357
  setBusy(true);
3256
3358
  try {
3257
3359
  await arbi.selectWorkspace(workspaceId);
3258
- const res = await arbi.documents.uploadFile(file, file.name, { folder });
3360
+ const file = new File([blob], name2, { type: blob.type });
3361
+ const res = await arbi.documents.uploadFile(file, name2, { folder });
3259
3362
  const id = res.doc_ext_ids?.[0];
3260
3363
  if (id) {
3261
3364
  onChange(asAssetRef(id));
@@ -3265,6 +3368,12 @@ function ImageFieldControl({
3265
3368
  setBusy(false);
3266
3369
  }
3267
3370
  };
3371
+ const onCropComplete = async (result) => {
3372
+ const src = cropSrc;
3373
+ setCropSrc(null);
3374
+ await uploadBlobAsAsset(result.blob, cropName);
3375
+ if (src?.startsWith("blob:")) URL.revokeObjectURL(src);
3376
+ };
3268
3377
  const onGenerate = async () => {
3269
3378
  if (!prompt.trim() || gen.isGenerating) return;
3270
3379
  const id = await gen.generate(prompt);
@@ -3282,7 +3391,22 @@ function ImageFieldControl({
3282
3391
  const inputClass = "w-full rounded-md border border-border bg-card px-2.5 py-1.5 text-sm text-foreground outline-none focus:border-primary";
3283
3392
  const tabClass = (on) => `inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium ${on ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"}`;
3284
3393
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", "data-testid": tp(), children: [
3285
- /* @__PURE__ */ jsxRuntime.jsx(BlockImage, { src: value, aspect: "16:9", rounded: "md", testId: tp("preview") }),
3394
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
3395
+ /* @__PURE__ */ jsxRuntime.jsx(BlockImage, { src: value, aspect: "16:9", rounded: "md", testId: tp("preview") }),
3396
+ currentAssetImage ? /* @__PURE__ */ jsxRuntime.jsxs(
3397
+ "button",
3398
+ {
3399
+ type: "button",
3400
+ onClick: openReframe,
3401
+ className: "absolute right-1.5 top-1.5 inline-flex items-center gap-1 rounded-md bg-background/85 px-2 py-1 text-xs font-medium text-foreground shadow-sm backdrop-blur hover:bg-background",
3402
+ "data-testid": tp("reframe"),
3403
+ children: [
3404
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Crop, { className: "size-3" }),
3405
+ " Crop"
3406
+ ]
3407
+ }
3408
+ ) : null
3409
+ ] }),
3286
3410
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: MODES.map((m) => /* @__PURE__ */ jsxRuntime.jsxs(
3287
3411
  "button",
3288
3412
  {
@@ -3399,6 +3523,19 @@ function ImageFieldControl({
3399
3523
  "data-testid": tp("clear"),
3400
3524
  children: "Clear"
3401
3525
  }
3526
+ ),
3527
+ /* @__PURE__ */ jsxRuntime.jsx(
3528
+ react.ImageCropModal,
3529
+ {
3530
+ open: !!cropSrc,
3531
+ image: cropSrc,
3532
+ title: "Frame your image",
3533
+ aspectOptions: react.DEFAULT_ASPECT_OPTIONS,
3534
+ cropShape: "rect",
3535
+ output: { maxSize: 1600 },
3536
+ onComplete: onCropComplete,
3537
+ onCancel: () => setCropSrc(null)
3538
+ }
3402
3539
  )
3403
3540
  ] });
3404
3541
  }
@@ -5490,6 +5627,7 @@ Object.defineProperty(exports, "toRedlineMarkdown", {
5490
5627
  exports.ASSET_REF_PREFIX = ASSET_REF_PREFIX;
5491
5628
  exports.Accordion = Accordion;
5492
5629
  exports.AgentsPanel = AgentsPanel;
5630
+ exports.AppHeader = AppHeader;
5493
5631
  exports.AppShell = AppShell;
5494
5632
  exports.AssetResolverProvider = AssetResolverProvider;
5495
5633
  exports.AvatarBlock = AvatarBlock;