@arronqzy/react-view 1.0.20 → 1.0.22

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.
@@ -201,6 +201,7 @@ function applyBlueprintAddNode(
201
201
  clock: "clock",
202
202
  fetch: "fetch",
203
203
  json: "json",
204
+ storage: "storage",
204
205
  and: "and",
205
206
  event: "event",
206
207
  };
@@ -6,6 +6,12 @@ import { buildChartOption, CHART_TYPES } from "../utils/chartOptionBuilder";
6
6
  import { PREVIEW_LAYOUT_EVENT } from "../utils/panelStateIO";
7
7
  import { TableNodeContent, type TableCellActionHandler } from "./table/TableNodeContent";
8
8
  import { comparePanelElementsPaintOrder } from "../utils/gridPlacement";
9
+ import {
10
+ isViewportNode,
11
+ normalizeViewportOverflow,
12
+ viewportOverflowStyle,
13
+ viewportWindowContentScale,
14
+ } from "../utils/viewportPlacement";
9
15
  import { cssTextLineHeight, cssTextAlignStyle } from "../utils/panelElementDefaults";
10
16
  import { mergeScene3dConfig } from "@arronqzy/view-scene3d";
11
17
  import { Scene3dNodeContent } from "@arronqzy/view-scene3d/react";
@@ -724,18 +730,30 @@ function ReferenceNodeContent({
724
730
 
725
731
  const innerW = Math.max(1, element.width);
726
732
  const innerH = Math.max(1, element.height);
727
- const scale = Math.max(0.05, Math.min(innerW / sourceWidth, innerH / sourceHeight));
728
- const mappedW = sourceWidth * scale;
729
- const mappedH = sourceHeight * scale;
733
+ const windowFit = isViewportNode(element);
734
+ const windowScale = windowFit
735
+ ? viewportWindowContentScale(element, allElements)
736
+ : { scaleX: 1, scaleY: 1 };
737
+ const scaleX = windowFit
738
+ ? windowScale.scaleX
739
+ : Math.max(0.05, Math.min(innerW / sourceWidth, innerH / sourceHeight));
740
+ const scaleY = windowFit
741
+ ? windowScale.scaleY
742
+ : scaleX;
743
+ const mappedW = sourceWidth * scaleX;
744
+ const mappedH = sourceHeight * scaleY;
730
745
 
731
746
  return {
732
747
  minX,
733
748
  minY,
734
- scale,
735
- offsetX: (innerW - mappedW) / 2,
736
- offsetY: (innerH - mappedH) / 2,
749
+ scaleX,
750
+ scaleY,
751
+ sourceWidth: mappedW,
752
+ sourceHeight: mappedH,
753
+ offsetX: windowFit ? 0 : (innerW - mappedW) / 2,
754
+ offsetY: windowFit ? 0 : (innerH - mappedH) / 2,
737
755
  };
738
- }, [element.height, element.width, sourceNodes]);
756
+ }, [allElements, element, sourceNodes]);
739
757
 
740
758
  if (!layout || sourceNodes.length === 0) {
741
759
  const hintText = element.refLayerId ? t("panel.config.refLayerEmpty") : t("panel.config.selectRefLayer");
@@ -748,20 +766,50 @@ function ReferenceNodeContent({
748
766
  );
749
767
  }
750
768
 
769
+ const overflowCss = isViewportNode(element)
770
+ ? viewportOverflowStyle(normalizeViewportOverflow(element.viewportOverflow))
771
+ : { overflowX: "hidden" as const, overflowY: "hidden" as const };
772
+ const canScroll =
773
+ isViewportNode(element) &&
774
+ (overflowCss.overflowX === "auto" || overflowCss.overflowY === "auto");
775
+
751
776
  return (
752
- <div className="pointer-events-none relative h-full w-full overflow-hidden">
777
+ <div
778
+ className="relative h-full w-full"
779
+ style={{
780
+ overflowX: overflowCss.overflowX,
781
+ overflowY: overflowCss.overflowY,
782
+ pointerEvents: isViewportNode(element) ? "auto" : "none",
783
+ }}
784
+ onWheel={canScroll ? (e) => e.stopPropagation() : undefined}
785
+ >
786
+ <div
787
+ className="pointer-events-none relative"
788
+ data-viewport-sizer={isViewportNode(element) ? "true" : undefined}
789
+ style={
790
+ isViewportNode(element)
791
+ ? {
792
+ width: layout.sourceWidth,
793
+ height: layout.sourceHeight,
794
+ minWidth: "100%",
795
+ minHeight: "100%",
796
+ }
797
+ : { width: "100%", height: "100%" }
798
+ }
799
+ >
753
800
  {sourceNodes.map((node) => {
754
801
  const box = getNodeAABB(node);
755
- const left = layout.offsetX + (box.left - layout.minX) * layout.scale;
756
- const top = layout.offsetY + (box.top - layout.minY) * layout.scale;
757
- const width = Math.max(12, node.width * layout.scale);
758
- const height = Math.max(10, node.height * layout.scale);
759
- const boxWidth = Math.max(12, (box.right - box.left) * layout.scale);
760
- const boxHeight = Math.max(10, (box.bottom - box.top) * layout.scale);
802
+ const left = layout.offsetX + (box.left - layout.minX) * layout.scaleX;
803
+ const top = layout.offsetY + (box.top - layout.minY) * layout.scaleY;
804
+ const width = Math.max(12, node.width * layout.scaleX);
805
+ const height = Math.max(10, node.height * layout.scaleY);
806
+ const boxWidth = Math.max(12, (box.right - box.left) * layout.scaleX);
807
+ const boxHeight = Math.max(10, (box.bottom - box.top) * layout.scaleY);
761
808
  return (
762
809
  <div
763
810
  key={node.id}
764
811
  className="absolute overflow-visible"
812
+ data-viewport-item={isViewportNode(element) ? "true" : undefined}
765
813
  style={{
766
814
  left,
767
815
  top,
@@ -771,6 +819,7 @@ function ReferenceNodeContent({
771
819
  >
772
820
  <div
773
821
  className="absolute"
822
+ data-viewport-item={isViewportNode(element) ? "true" : undefined}
774
823
  style={{
775
824
  left: (boxWidth - width) / 2,
776
825
  top: (boxHeight - height) / 2,
@@ -783,7 +832,7 @@ function ReferenceNodeContent({
783
832
  >
784
833
  {CHART_TYPES.has(node.materialType ?? "") ? (
785
834
  <ChartNodeContent element={node} />
786
- ) : node.materialType === "reference" ? (
835
+ ) : node.materialType === "reference" || node.materialType === "viewport" ? (
787
836
  <ReferenceNodeContent
788
837
  element={node}
789
838
  allElements={allElements}
@@ -803,6 +852,7 @@ function ReferenceNodeContent({
803
852
  </div>
804
853
  );
805
854
  })}
855
+ </div>
806
856
  </div>
807
857
  );
808
858
  }
@@ -887,6 +937,7 @@ export const ElementsLayer = React.memo(function ElementsLayer({
887
937
  zIndex: el.zIndex ?? 1,
888
938
  transform: `rotate(${el.rotate ?? 0}deg)`,
889
939
  transformOrigin: "center center",
940
+ overflow: el.materialType === "viewport" ? "hidden" : undefined,
890
941
  boxSizing: "border-box",
891
942
  ...getNodeVisualStyle(el),
892
943
  }}
@@ -897,7 +948,7 @@ export const ElementsLayer = React.memo(function ElementsLayer({
897
948
  previewLayoutKey={previewLayoutKey}
898
949
  previewMode={previewMode}
899
950
  />
900
- ) : el.materialType === "reference" ? (
951
+ ) : el.materialType === "reference" || el.materialType === "viewport" ? (
901
952
  <ReferenceNodeContent element={el} allElements={allElements} />
902
953
  ) : el.materialType === "grid" ? (
903
954
  <GridNodeContent
@@ -63,6 +63,7 @@ const MATERIAL_LABEL_KEYS: Record<string, string> = {
63
63
  text: "panel.material.text",
64
64
  rect: "panel.material.rect",
65
65
  grid: "panel.material.grid",
66
+ viewport: "panel.material.viewport",
66
67
  image: "panel.material.image",
67
68
  video: "panel.material.video",
68
69
  audio: "panel.material.audio",
@@ -374,6 +375,18 @@ function MaterialPreview({ id }: { id: string }) {
374
375
  </div>
375
376
  );
376
377
  }
378
+ if (id === "viewport") {
379
+ return (
380
+ <div className={common}>
381
+ <div className="absolute inset-1.5 overflow-hidden rounded-sm border border-primary/70 bg-primary/10">
382
+ <div className="absolute left-1 top-1 h-6 w-8 rounded-[2px] bg-primary/45" />
383
+ <div className="absolute left-6 top-4 h-7 w-10 rounded-[2px] bg-primary/30" />
384
+ <div className="absolute bottom-0 right-0 h-1.5 w-6 rounded-tl bg-primary/55" />
385
+ <div className="absolute bottom-0 right-0 top-2 w-1.5 rounded-l bg-primary/40" />
386
+ </div>
387
+ </div>
388
+ );
389
+ }
377
390
 
378
391
  return <div className={common} />;
379
392
  }
@@ -403,6 +416,7 @@ function getDefaultCategories(t: (key: string) => string): MaterialCategory[] {
403
416
  { id: "geometry", title: t("panel.material.geometry") },
404
417
  { id: "scene3d", title: t("panel.material.scene3d") },
405
418
  { id: "grid", title: t("panel.material.grid") },
419
+ { id: "viewport", title: t("panel.material.viewport") },
406
420
  { id: "image", title: t("panel.material.image") },
407
421
  { id: "reference", title: t("panel.material.reference") },
408
422
  ],
@@ -568,9 +582,8 @@ export function MaterialSidebar({
568
582
  };
569
583
 
570
584
  const getNodeChildren = (node: PanelElement, sourceOverride?: PanelElement[]) => {
571
- const isRef = node.materialType === "reference";
585
+ const isRef = node.materialType === "reference" || node.materialType === "viewport";
572
586
  const isGrid = node.materialType === "grid";
573
- // 必须用节点自身所在图层查找子节点;外层传入的 layerId 在深嵌套/引用子树中会错位,导致子节点不显示在对应网格下
574
587
  const gridChildren = isGrid
575
588
  ? [...(childrenByGridByLayer.get(node.layerId)?.get(node.id) ?? [])].sort(
576
589
  compareGridTreeChildOrder
@@ -611,7 +624,7 @@ export function MaterialSidebar({
611
624
  ) => {
612
625
  const selected = selectedIds.includes(node.id);
613
626
  if (!nodeMatchesTreeSearch(node, visited, sourceOverride)) return null;
614
- const isRef = node.materialType === "reference";
627
+ const isRef = node.materialType === "reference" || node.materialType === "viewport";
615
628
  const refMode = node.refCopyMode ?? "shallow";
616
629
  const isDeepRef = isRef && refMode === "deep";
617
630
  const children = getNodeChildren(node, sourceOverride);
@@ -829,17 +842,11 @@ export function MaterialSidebar({
829
842
  };
830
843
 
831
844
  const hasRefInSubtree = (node: PanelElement, visited: Set<string>): boolean => {
832
- if (node.materialType === "reference") return true;
833
- if (node.materialType !== "reference") return false;
845
+ if (node.materialType === "reference" || node.materialType === "viewport") return true;
834
846
  if (visited.has(node.id)) return false;
835
847
  const nextVisited = new Set(visited);
836
848
  nextVisited.add(node.id);
837
- const children =
838
- node.refCopyMode === "deep"
839
- ? node.refSnapshot ?? []
840
- : node.refLayerId
841
- ? elementsByLayer.get(node.refLayerId) ?? []
842
- : [];
849
+ const children = getNodeChildren(node);
843
850
  return children.some((child) => hasRefInSubtree(child, nextVisited));
844
851
  };
845
852
 
@@ -28,6 +28,11 @@ import type {
28
28
  ReferenceCopyMode,
29
29
  } from "../types";
30
30
  import { buildChartOption, CHART_TYPES, getChartLabelsDisplayText, getChartValuesDisplayText } from "../utils/chartOptionBuilder";
31
+ import {
32
+ VIEWPORT_OVERFLOW_MODES,
33
+ normalizeViewportOverflow,
34
+ type ViewportOverflowMode,
35
+ } from "../utils/viewportPlacement";
31
36
  import type { PanelLayer } from "../types";
32
37
  import { getPanelMessages } from "../constants/messages";
33
38
  import { ViewElementScopePanel } from "./ViewElementScopePanel";
@@ -1863,7 +1868,7 @@ export function PanelConfigSidebar({
1863
1868
  </label>
1864
1869
  </div>
1865
1870
  ) : null}
1866
- {el.materialType === "reference" ? (
1871
+ {el.materialType === "reference" || el.materialType === "viewport" ? (
1867
1872
  <div className="grid grid-cols-2 gap-2">
1868
1873
  <label className="block space-y-1.5">
1869
1874
  <div>{t("panel.config.refLayer")}</div>
@@ -1895,6 +1900,34 @@ export function PanelConfigSidebar({
1895
1900
  </label>
1896
1901
  </div>
1897
1902
  ) : null}
1903
+ {el.materialType === "viewport" ? (
1904
+ <label className="block space-y-1.5">
1905
+ <div>{t("panel.config.viewportOverflow")}</div>
1906
+ <Select
1907
+ value={normalizeViewportOverflow(el.viewportOverflow)}
1908
+ onValueChange={(value) =>
1909
+ updateElement(el.id, {
1910
+ viewportOverflow: value as ViewportOverflowMode,
1911
+ })
1912
+ }
1913
+ >
1914
+ <SelectTrigger className="h-7"><SelectValue /></SelectTrigger>
1915
+ <SelectContent>
1916
+ {VIEWPORT_OVERFLOW_MODES.map((mode) => (
1917
+ <SelectItem key={mode} value={mode}>
1918
+ {mode === "scroll-x"
1919
+ ? t("panel.config.viewportOverflowScrollX")
1920
+ : mode === "scroll-y"
1921
+ ? t("panel.config.viewportOverflowScrollY")
1922
+ : mode === "scroll"
1923
+ ? t("panel.config.viewportOverflowScroll")
1924
+ : t("panel.config.viewportOverflowClip")}
1925
+ </SelectItem>
1926
+ ))}
1927
+ </SelectContent>
1928
+ </Select>
1929
+ </label>
1930
+ ) : null}
1898
1931
  </div>
1899
1932
  </fieldset>
1900
1933
  </CardContent>
@@ -3603,6 +3636,111 @@ export function PanelConfigSidebar({
3603
3636
  {t("panel.config.gridHint")}
3604
3637
  </>
3605
3638
  )
3639
+ ) : selectedElement.materialType === "viewport" ? (
3640
+ renderSection(
3641
+ "viewportConfig",
3642
+ t("panel.config.sectionViewport"),
3643
+ <>
3644
+ {renderFieldGroup(
3645
+ t("panel.config.groupRefSource"),
3646
+ <label className="block space-y-1">
3647
+ <div>{t("panel.config.refLayer")}</div>
3648
+ <Select
3649
+ value={selectedElement.refLayerId ?? "__none__"}
3650
+ onValueChange={(value) =>
3651
+ updateElement(selectedElement.id, {
3652
+ refLayerId: value === "__none__" ? undefined : value,
3653
+ })
3654
+ }
3655
+ >
3656
+ <SelectTrigger className="h-7">
3657
+ <SelectValue placeholder={t("panel.config.pleaseSelectLayer")} />
3658
+ </SelectTrigger>
3659
+ <SelectContent>
3660
+ <SelectItem value="__none__">{t("panel.config.noneNoRef")}</SelectItem>
3661
+ {layers
3662
+ .filter((l) => l.id !== selectedElement.layerId)
3663
+ .map((l) => (
3664
+ <SelectItem key={l.id} value={l.id}>
3665
+ {l.name}
3666
+ </SelectItem>
3667
+ ))}
3668
+ </SelectContent>
3669
+ </Select>
3670
+ </label>
3671
+ )}
3672
+ {renderFieldGroup(
3673
+ t("panel.config.groupCopyStrategy"),
3674
+ <>
3675
+ <label className="block space-y-1">
3676
+ <div>{t("panel.config.copyMode")}</div>
3677
+ <Select
3678
+ value={selectedElement.refCopyMode ?? "shallow"}
3679
+ onValueChange={(value) =>
3680
+ setReferenceCopyMode?.(
3681
+ selectedElement.id,
3682
+ value as ReferenceCopyMode
3683
+ )
3684
+ }
3685
+ >
3686
+ <SelectTrigger className="h-7">
3687
+ <SelectValue placeholder={t("panel.config.selectCopyMode")} />
3688
+ </SelectTrigger>
3689
+ <SelectContent>
3690
+ <SelectItem value="shallow">{t("panel.config.shallowFollow")}</SelectItem>
3691
+ <SelectItem value="deep">{t("panel.config.deepFreeze")}</SelectItem>
3692
+ </SelectContent>
3693
+ </Select>
3694
+ </label>
3695
+ </>,
3696
+ <>{t("panel.config.copyStrategyHint")}</>,
3697
+ { groupKey: "viewportCopyStrategy", defaultOpen: false }
3698
+ )}
3699
+ {renderFieldGroup(
3700
+ t("panel.config.groupViewportOverflow"),
3701
+ <label className="block space-y-1">
3702
+ <div>{t("panel.config.viewportOverflow")}</div>
3703
+ <Select
3704
+ value={normalizeViewportOverflow(selectedElement.viewportOverflow)}
3705
+ onValueChange={(value) =>
3706
+ updateElement(selectedElement.id, {
3707
+ viewportOverflow: value as ViewportOverflowMode,
3708
+ })
3709
+ }
3710
+ >
3711
+ <SelectTrigger className="h-7">
3712
+ <SelectValue placeholder={t("panel.config.viewportOverflow")} />
3713
+ </SelectTrigger>
3714
+ <SelectContent>
3715
+ {VIEWPORT_OVERFLOW_MODES.map((mode) => (
3716
+ <SelectItem key={mode} value={mode}>
3717
+ {mode === "scroll-x"
3718
+ ? t("panel.config.viewportOverflowScrollX")
3719
+ : mode === "scroll-y"
3720
+ ? t("panel.config.viewportOverflowScrollY")
3721
+ : mode === "scroll"
3722
+ ? t("panel.config.viewportOverflowScroll")
3723
+ : t("panel.config.viewportOverflowClip")}
3724
+ </SelectItem>
3725
+ ))}
3726
+ </SelectContent>
3727
+ </Select>
3728
+ </label>
3729
+ )}
3730
+ </>,
3731
+ true,
3732
+ [
3733
+ t("panel.config.searchKwViewport"),
3734
+ "viewport",
3735
+ t("panel.config.refLayer"),
3736
+ t("panel.config.viewportOverflow"),
3737
+ t("panel.config.viewportOverflowScrollX"),
3738
+ t("panel.config.viewportOverflowClip"),
3739
+ t("panel.material.shallowCopy"),
3740
+ t("panel.material.deepCopy"),
3741
+ ],
3742
+ <>{t("panel.config.viewportHint")}</>
3743
+ )
3606
3744
  ) : selectedElement.materialType === "reference" ? (
3607
3745
  renderSection(
3608
3746
  "reference",
@@ -62,6 +62,7 @@ export type WorkspaceConfigSidebarProps = Omit<
62
62
  | "lifecyclePhase"
63
63
  | "fetchConfig"
64
64
  | "jsonConfig"
65
+ | "storageConfig"
65
66
  | "logicConfig"
66
67
  | "clockConfig"
67
68
  | "eventConfig"
@@ -16,6 +16,7 @@ import {
16
16
  getDefaultChartConfig,
17
17
  getDefaultTableConfig,
18
18
  getDefaultGridConfig,
19
+ getDefaultViewportConfig,
19
20
  getDefaultLayer,
20
21
  getDefaultNodeName,
21
22
  getDefaultScene3dConfigForMaterial,
@@ -552,7 +553,10 @@ export function usePanelElements() {
552
553
  .filter((n) => isPanelElementNode(n) && n.props)
553
554
  .map((n) => n.props as PanelElement);
554
555
  const desiredMode = options?.referenceCopyMode;
555
- if (nextProps.materialType === "reference" && desiredMode) {
556
+ if (
557
+ (nextProps.materialType === "reference" || nextProps.materialType === "viewport") &&
558
+ desiredMode
559
+ ) {
556
560
  if (desiredMode === "deep" && nextProps.refLayerId) {
557
561
  nextProps.refCopyMode = "deep";
558
562
  nextProps.refSnapshot = buildDeepReferenceSnapshot(all, nextProps.refLayerId);
@@ -596,7 +600,9 @@ export function usePanelElements() {
596
600
  name: getDefaultNodeName(materialType, t),
597
601
  ...getDefaultTextContent(materialType, t),
598
602
  ...getDefaultGridConfig(materialType),
599
- refCopyMode: materialType === "reference" ? "shallow" : undefined,
603
+ ...getDefaultViewportConfig(materialType),
604
+ refCopyMode:
605
+ materialType === "reference" || materialType === "viewport" ? "shallow" : undefined,
600
606
  chart: getDefaultChartConfig(materialType, t),
601
607
  table: getDefaultTableConfig(materialType, t),
602
608
  geometryShape: materialType === "geometry" ? "rect" : undefined,
@@ -700,7 +706,7 @@ export function usePanelElements() {
700
706
  .filter((n) => isPanelElementNode(n) && n.props)
701
707
  .map((n) => n.props as PanelElement);
702
708
  const target = all.find((el) => el.id === id);
703
- if (!target || target.materialType !== "reference") return;
709
+ if (!target || (target.materialType !== "reference" && target.materialType !== "viewport")) return;
704
710
  const currentLayers =
705
711
  (current.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
706
712
  const layer = currentLayers.find((l) => l.id === target.layerId);
@@ -895,7 +901,7 @@ export function usePanelElements() {
895
901
  if (target.isMapping) return false;
896
902
  const willBeDeleted = mode === "remove" && el.layerId === layerId;
897
903
  if (willBeDeleted) return false;
898
- if (el.materialType !== "reference") return false;
904
+ if (el.materialType !== "reference" && el.materialType !== "viewport") return false;
899
905
  if (el.refLayerId !== layerId) return false;
900
906
  return (el.refCopyMode ?? "shallow") !== "deep";
901
907
  });