@osamash/floor-planner 1.2.5 → 1.2.7
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/floor-planner.css +1 -1
- package/dist/floor-planner.es.js +8799 -7754
- package/dist/floor-planner.umd.js +44 -44
- package/dist/types/react-planner/ai-chat/AIChatPanel.d.ts +3 -2
- package/dist/types/react-planner/ai-chat/aiService.d.ts +49 -4
- package/dist/types/react-planner/ai-chat/furnish.d.ts +36 -0
- package/dist/types/react-planner/ai-chat/layoutPacker.d.ts +114 -0
- package/dist/types/react-planner/ai-chat/plannerActions.d.ts +0 -15
- package/dist/types/react-planner/constants.d.ts +13 -0
- package/dist/types/react-planner/hooks/usePlanThumbnail.d.ts +12 -3
- package/dist/types/react-planner/plugins/keyboard.d.ts +1 -1
- package/dist/types/react-planner/toolbar/Toolbar.d.ts +3 -1
- package/dist/types/react-planner/toolbar/ToolbarRail.d.ts +1 -1
- package/dist/types/react-planner/toolbar/ToolbarSubPanel.d.ts +7 -1
- package/dist/types/react-planner/toolbar/ZoomControls.d.ts +2 -2
- package/dist/types/react-planner/utils/visual-change.d.ts +12 -0
- package/package.json +1 -1
- package/dist/types/react-planner/ai-chat/AIChatBubble.d.ts +0 -6
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type AIRequestHandler } from "./aiService";
|
|
2
2
|
interface AIChatPanelProps {
|
|
3
|
+
/** Whether the panel is the visible toolbar section. It stays mounted when
|
|
4
|
+
it isn't, so this drives focus and the microphone, not rendering. */
|
|
3
5
|
open: boolean;
|
|
4
|
-
onClose: () => void;
|
|
5
6
|
onAIMessage?: AIRequestHandler;
|
|
6
7
|
}
|
|
7
|
-
declare const AIChatPanel: ({ open,
|
|
8
|
+
declare const AIChatPanel: ({ open, onAIMessage }: AIChatPanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
9
|
export default AIChatPanel;
|
|
@@ -38,9 +38,16 @@ export interface WallInfo {
|
|
|
38
38
|
/** cm. */
|
|
39
39
|
thickness: number | null;
|
|
40
40
|
height: number | null;
|
|
41
|
-
/** The wall's two faces. Which one looks into a given room is geometric
|
|
41
|
+
/** The wall's two faces. Which one looks into a given room is geometric, so
|
|
42
|
+
* `areaA`/`areaB` name the area each face looks into — null means open air,
|
|
43
|
+
* i.e. that face is the outside of the building. Ground surfaces such as a
|
|
44
|
+
* garden are not counted; a wall facing one still has an outside face. */
|
|
42
45
|
textureA: string | null;
|
|
43
46
|
textureB: string | null;
|
|
47
|
+
colorA: string | null;
|
|
48
|
+
colorB: string | null;
|
|
49
|
+
areaA: string | null;
|
|
50
|
+
areaB: string | null;
|
|
44
51
|
}
|
|
45
52
|
export interface HoleInfo {
|
|
46
53
|
id: string;
|
|
@@ -155,7 +162,39 @@ export type RoomSide = "left" | "right" | "above" | "below";
|
|
|
155
162
|
* `ACTION_SPECS` (what the model may say), and a branch in
|
|
156
163
|
* `applyPlannerActions` (what actually happens).
|
|
157
164
|
*/
|
|
158
|
-
export type PlannerAction = AddRoomAction | SetRoomPropertiesAction | AddHoleAction | AddItemAction | SetWallPropertiesAction | SetItemPropertiesAction | AddSurfaceAction | DeleteAction | SetSurfacePropertiesAction;
|
|
165
|
+
export type PlannerAction = GenerateLayoutAction | AddRoomAction | SetRoomPropertiesAction | AddHoleAction | AddItemAction | SetWallPropertiesAction | SetItemPropertiesAction | AddSurfaceAction | DeleteAction | SetSurfacePropertiesAction;
|
|
166
|
+
/**
|
|
167
|
+
* A whole floor plan as a program: which rooms, of what type, roughly how big.
|
|
168
|
+
* No geometry — the packer works out every rectangle. Applying this REPLACES
|
|
169
|
+
* whatever is on the canvas.
|
|
170
|
+
*/
|
|
171
|
+
export type GenerateLayoutAction = {
|
|
172
|
+
type: "generate-layout";
|
|
173
|
+
rooms: {
|
|
174
|
+
name: string;
|
|
175
|
+
roomType: string;
|
|
176
|
+
size?: "small" | "medium" | "large";
|
|
177
|
+
}[];
|
|
178
|
+
/** A roomCategory value; the room types must belong to it. Backend defaults
|
|
179
|
+
* this to "house", so it always arrives populated. */
|
|
180
|
+
buildingType: string;
|
|
181
|
+
/** Ground outside the building — garden, pool, patio. Not rooms: no walls,
|
|
182
|
+
* so no doors, windows or furniture. The packer picks which outside wall
|
|
183
|
+
* each one lies against. */
|
|
184
|
+
outdoor?: {
|
|
185
|
+
name: string;
|
|
186
|
+
texture?: string;
|
|
187
|
+
color?: string;
|
|
188
|
+
depth?: number;
|
|
189
|
+
}[];
|
|
190
|
+
/** m², if the user gave one. Absent means the packer sizes the plan itself. */
|
|
191
|
+
totalArea?: number;
|
|
192
|
+
/** Only the building OUTLINE varies — every room stays a rectangle. Absent
|
|
193
|
+
* means the packer picks from the seed. */
|
|
194
|
+
footprint?: "rectangle" | "L" | "U";
|
|
195
|
+
/** Always populated backend-side, so the same action replays identically. */
|
|
196
|
+
seed: number;
|
|
197
|
+
};
|
|
159
198
|
export type AddRoomAction = {
|
|
160
199
|
type: "add-room";
|
|
161
200
|
/**
|
|
@@ -201,6 +240,9 @@ export type AddHoleAction = {
|
|
|
201
240
|
* and land as two doors. Mutually exclusive with room/side.
|
|
202
241
|
*/
|
|
203
242
|
between?: [string, string];
|
|
243
|
+
/** Where along the wall, 1-8. Absent means centred, and the frontend then
|
|
244
|
+
* spaces multiple openings on one wall evenly. */
|
|
245
|
+
at?: number;
|
|
204
246
|
/** A particular wall — the only way to reach an exterior one. */
|
|
205
247
|
room?: string;
|
|
206
248
|
side?: RoomSide;
|
|
@@ -222,10 +264,13 @@ export type SetWallPropertiesAction = {
|
|
|
222
264
|
/** A room name, or "selected". All four of its walls unless `side` is given. */
|
|
223
265
|
room: string;
|
|
224
266
|
side?: RoomSide;
|
|
225
|
-
/**
|
|
267
|
+
/** Which of the wall's two faces to paint. "outside" only works on a wall
|
|
268
|
+
* with open air behind it; a wall shared with another room is skipped. */
|
|
269
|
+
face?: "inside" | "outside";
|
|
226
270
|
color?: string;
|
|
227
271
|
texture?: string;
|
|
228
|
-
/** cm. Whole-wall, so a shared wall changes for both
|
|
272
|
+
/** cm. Whole-wall rather than per-face, so a shared wall changes for both
|
|
273
|
+
* rooms and neither can be combined with `face: "outside"`. */
|
|
229
274
|
thickness?: number;
|
|
230
275
|
height?: number;
|
|
231
276
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Furnishing a generated plan.
|
|
3
|
+
*
|
|
4
|
+
* This lives in the frontend rather than the packer because it needs the
|
|
5
|
+
* catalog: what furniture exists, what it is tagged as, and how big it renders.
|
|
6
|
+
*
|
|
7
|
+
* A note on sizes. The catalog ships almost every item as a 100x100 placeholder
|
|
8
|
+
* — a double bed, a bedside cabinet and a TV have the same footprint — and
|
|
9
|
+
* neither renderer honours a declared width/depth, only a single uniform
|
|
10
|
+
* `scale` (viewer2d/Item.tsx measures the drawn geometry; viewer3d sets
|
|
11
|
+
* pivot.scale to one value on all three axes). So a role's size is expressed as
|
|
12
|
+
* a scale, and clearances are measured against the footprint that produces.
|
|
13
|
+
* Furniture is therefore square-ish but honestly sized.
|
|
14
|
+
*/
|
|
15
|
+
type Rect = {
|
|
16
|
+
x0: number;
|
|
17
|
+
y0: number;
|
|
18
|
+
x1: number;
|
|
19
|
+
y1: number;
|
|
20
|
+
};
|
|
21
|
+
export type Furnishing = {
|
|
22
|
+
itemType: string;
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
rotation: number;
|
|
26
|
+
scale: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Furniture for one room: what goes in it, where, and facing which way.
|
|
30
|
+
*
|
|
31
|
+
* Roles are placed in order of importance, each reserving its own footprint AND
|
|
32
|
+
* the walking space in front of it, so later items cannot land in the space the
|
|
33
|
+
* bed needs to be got into.
|
|
34
|
+
*/
|
|
35
|
+
export declare function furnishRoom(planner: any, layerID: string, roomType: string, room: Rect, walls: Record<string, string[]>, seed: number): Furnishing[];
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a room program — which rooms, of what type, roughly how big — into
|
|
3
|
+
* rectangles. The agent never sends coordinates, so this is where a plan
|
|
4
|
+
* actually acquires geometry.
|
|
5
|
+
*
|
|
6
|
+
* Rooms stay axis-aligned rectangles; the BUILDING outline is what varies,
|
|
7
|
+
* built by packing those rectangles into one, two or three wings.
|
|
8
|
+
*/
|
|
9
|
+
export type Rect = {
|
|
10
|
+
x0: number;
|
|
11
|
+
y0: number;
|
|
12
|
+
x1: number;
|
|
13
|
+
y1: number;
|
|
14
|
+
};
|
|
15
|
+
export type ProgramRoom = {
|
|
16
|
+
name: string;
|
|
17
|
+
roomType: string;
|
|
18
|
+
size?: "small" | "medium" | "large";
|
|
19
|
+
/** Other rooms in the same program this one should sit beside, or not. The
|
|
20
|
+
* user's own wishes; the RELATIONS table below covers the architecture. */
|
|
21
|
+
near?: string[];
|
|
22
|
+
away?: string[];
|
|
23
|
+
};
|
|
24
|
+
export type PackedRoom = ProgramRoom & {
|
|
25
|
+
rect: Rect;
|
|
26
|
+
};
|
|
27
|
+
export type PackOptions = {
|
|
28
|
+
totalArea?: number;
|
|
29
|
+
footprint?: "rectangle" | "L" | "U";
|
|
30
|
+
seed: number;
|
|
31
|
+
/** Room types valid for this building type — a hallway is only added if the
|
|
32
|
+
* catalog has somewhere to put it. */
|
|
33
|
+
validTypes: Set<string>;
|
|
34
|
+
/** Scene centre, in cm. The footprint is centred on it. */
|
|
35
|
+
centre: {
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
};
|
|
39
|
+
/** Gardens, pools and patios to lay against the outside of the building. */
|
|
40
|
+
outdoor?: OutdoorRequest[];
|
|
41
|
+
};
|
|
42
|
+
/** Narrower than this and a room stops being usable — the caller reports these
|
|
43
|
+
* rather than dropping them silently, since the user asked for them. */
|
|
44
|
+
export declare const MIN_SIDE = 150;
|
|
45
|
+
/** A corridor is legitimately narrower than a room. Callers must use this
|
|
46
|
+
* rather than MIN_SIDE directly, or they drop the hallway and leave the two
|
|
47
|
+
* halves of the plan with no way between them. */
|
|
48
|
+
export declare const minSideFor: (roomType: string) => 150 | 130;
|
|
49
|
+
export type Side = "left" | "right" | "above" | "below";
|
|
50
|
+
/**
|
|
51
|
+
* An opening the plan needs. Expressed in the same terms `add-hole` already
|
|
52
|
+
* uses — two room names, or one room and a side — so the frontend resolves it
|
|
53
|
+
* with the wall-finding it already has.
|
|
54
|
+
*/
|
|
55
|
+
export type Opening = {
|
|
56
|
+
kind: "interior";
|
|
57
|
+
a: string;
|
|
58
|
+
b: string;
|
|
59
|
+
holeType: string;
|
|
60
|
+
} | {
|
|
61
|
+
kind: "entrance";
|
|
62
|
+
room: string;
|
|
63
|
+
side: Side;
|
|
64
|
+
holeType: string;
|
|
65
|
+
at: number;
|
|
66
|
+
} | {
|
|
67
|
+
kind: "window";
|
|
68
|
+
room: string;
|
|
69
|
+
side: Side;
|
|
70
|
+
holeType: string;
|
|
71
|
+
at: number;
|
|
72
|
+
width: number;
|
|
73
|
+
height: number;
|
|
74
|
+
altitude: number;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Every opening the plan needs: one front door, and a spanning tree of interior
|
|
78
|
+
* doors reaching every room from it.
|
|
79
|
+
*
|
|
80
|
+
* A spanning tree rather than a door on every shared wall — a 9-room house has
|
|
81
|
+
* around twenty adjacencies and should have about ten doors. Edges are taken
|
|
82
|
+
* cheapest-first, and a corridor edge always costs zero, so rooms are reached
|
|
83
|
+
* off the hall rather than through each other whenever the hall touches them.
|
|
84
|
+
*/
|
|
85
|
+
export declare function planDoors(packed: PackedRoom[], seed?: number): Opening[];
|
|
86
|
+
export type OutdoorRequest = {
|
|
87
|
+
name: string;
|
|
88
|
+
texture?: string;
|
|
89
|
+
color?: string;
|
|
90
|
+
depth?: number;
|
|
91
|
+
};
|
|
92
|
+
export type OutdoorSurface = OutdoorRequest & {
|
|
93
|
+
/** The room whose wall it lies against, and which side. */
|
|
94
|
+
of: string;
|
|
95
|
+
side: Side;
|
|
96
|
+
depth: number;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Where the garden, the pool and the patio go.
|
|
100
|
+
*
|
|
101
|
+
* A surface runs the FULL width of the wall it lies against, so a side that is
|
|
102
|
+
* only partly exterior is no good — the surface would be laid straight over the
|
|
103
|
+
* neighbouring room. Longest wall first, never the entrance elevation (a pool
|
|
104
|
+
* across the front door is nobody's idea of a villa), and never overlapping a
|
|
105
|
+
* surface already placed.
|
|
106
|
+
*/
|
|
107
|
+
export declare function planOutdoor(packed: PackedRoom[], wanted: OutdoorRequest[], openings: Opening[]): OutdoorSurface[];
|
|
108
|
+
export declare function packLayout(program: ProgramRoom[], opts: PackOptions): {
|
|
109
|
+
rooms: PackedRoom[];
|
|
110
|
+
openings: Opening[];
|
|
111
|
+
surfaces: OutdoorSurface[];
|
|
112
|
+
};
|
|
113
|
+
/** Rooms sharing enough wall for a door. Anything less is a corner touch. */
|
|
114
|
+
export declare function adjacencyOf(packed: PackedRoom[]): Map<string, PackedRoom[]>;
|
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
import type { PlanSummary, PlannerAction } from "./aiService";
|
|
2
2
|
export declare function summarizePlan(planner: any): PlanSummary;
|
|
3
|
-
/**
|
|
4
|
-
* Applies a batch of actions from the agent to the planner.
|
|
5
|
-
*
|
|
6
|
-
* The agent never sends coordinates. It says how big a room is, what to call
|
|
7
|
-
* it, and which existing room it sits next to — every number describing a
|
|
8
|
-
* POSITION is worked out here, where it can be exact. A room with no `of` /
|
|
9
|
-
* `side` is centered in the scene, which is only ever right for the first room.
|
|
10
|
-
*
|
|
11
|
-
* Rooms created earlier in this same batch are valid anchors: each one is
|
|
12
|
-
* looked up by name from live state after it is created, so a five-room plan
|
|
13
|
-
* arrives as five actions that chain off each other.
|
|
14
|
-
*
|
|
15
|
-
* @returns `skipped` — human-readable reasons anything was not applied, shown
|
|
16
|
-
* to the user by the caller.
|
|
17
|
-
*/
|
|
18
3
|
export declare function applyPlannerActions(actions: PlannerAction[]): {
|
|
19
4
|
skipped: string[];
|
|
20
5
|
};
|
|
@@ -220,6 +220,19 @@ export declare const MODE_FITTING_IMAGE = "MODE_FITTING_IMAGE";
|
|
|
220
220
|
export declare const MODE_VIEWING_CATALOG = "MODE_VIEWING_CATALOG";
|
|
221
221
|
export declare const MODE_CONFIGURING_PROJECT = "MODE_CONFIGURING_PROJECT";
|
|
222
222
|
export declare const MODE_SNAPPING: string[];
|
|
223
|
+
/**
|
|
224
|
+
* The modes in which `Content` mounts the 2D viewer — i.e. the plan `<svg>` is
|
|
225
|
+
* in the DOM. Every other mode shows the 3D viewer, the catalog list or
|
|
226
|
+
* nothing, so there is no plan to screenshot.
|
|
227
|
+
*/
|
|
228
|
+
export declare const MODE_2D_VIEWER: string[];
|
|
229
|
+
/**
|
|
230
|
+
* The subset of `MODE_2D_VIEWER` where a gesture is mid-flight. The scene
|
|
231
|
+
* legitimately holds preview geometry in these — a wall that follows the
|
|
232
|
+
* cursor, an unfinished surface polygon, an item being dragged — so they're
|
|
233
|
+
* states to let settle rather than to capture as finished work.
|
|
234
|
+
*/
|
|
235
|
+
export declare const MODE_IN_PROGRESS: string[];
|
|
223
236
|
export declare const UNIT_MILLIMETER = "mm";
|
|
224
237
|
export declare const UNIT_CENTIMETER = "cm";
|
|
225
238
|
export declare const UNIT_METER = "m";
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Keeps a plan thumbnail up to date, handing each new one to `onReady`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Every visual change to the 2D view schedules a capture — adding or removing a
|
|
5
|
+
* room, an item, a wall, a hole, toggling dimensions or the grid. A capture is
|
|
6
|
+
* only *taken* once the view is in a state worth keeping: not while the 3D
|
|
7
|
+
* viewer or the catalog is up (there's no plan SVG mounted at all then), and
|
|
8
|
+
* not mid-gesture, when the scene still holds preview geometry. When the moment
|
|
9
|
+
* is wrong the change stays pending and the mode change that makes it right
|
|
10
|
+
* re-arms it, so a room added and then admired in 3D still gets its screenshot.
|
|
11
|
+
*
|
|
12
|
+
* Capture is also deliberately *ahead* of unload, not during it. `beforeunload`
|
|
5
13
|
* can't await anything, and a multi-hundred-KB data URI is far past what
|
|
6
14
|
* sendBeacon or fetch({ keepalive: true }) will carry (~64KB), so there is no
|
|
7
15
|
* way to both build and ship an image once the page is going away. Instead we
|
|
8
|
-
* capture on
|
|
9
|
-
*
|
|
16
|
+
* capture on a short settle timer, and flush anything still pending when the
|
|
17
|
+
* page is hidden or the planner unmounts — by the time the tab actually closes,
|
|
18
|
+
* the consumer already has a current image.
|
|
10
19
|
*/
|
|
11
20
|
export declare function usePlanThumbnail(onReady?: (img: string) => void): {
|
|
12
21
|
ensureScreenshot: (force?: boolean) => Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactPlannerStore } from "../../store/ReactPlannerStore";
|
|
2
2
|
export default function keyboard(): (store: ReactPlannerStore) => () => void;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type Catalog from "../catalog/catalog";
|
|
2
|
+
import type { AIRequestHandler } from "../ai-chat/aiService";
|
|
2
3
|
interface ToolbarProps {
|
|
3
4
|
catalog: Catalog;
|
|
4
5
|
has3dView?: boolean;
|
|
6
|
+
onAIMessage?: AIRequestHandler;
|
|
5
7
|
}
|
|
6
|
-
declare function Toolbar({ catalog, has3dView }: ToolbarProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare function Toolbar({ catalog, has3dView, onAIMessage }: ToolbarProps): import("react/jsx-runtime").JSX.Element;
|
|
7
9
|
export default Toolbar;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
export type RailSection = "project" | "build" | "info" | "objects" | "styleboards" | "finishes" | "help";
|
|
2
|
+
export type RailSection = "project" | "build" | "info" | "objects" | "ai" | "styleboards" | "finishes" | "help";
|
|
3
3
|
export interface RailItem {
|
|
4
4
|
key: RailSection;
|
|
5
5
|
label: string;
|
|
@@ -11,6 +11,12 @@ export interface SubPanelOption {
|
|
|
11
11
|
}
|
|
12
12
|
interface ToolbarSubPanelProps {
|
|
13
13
|
title: string;
|
|
14
|
+
/** Fills the panel below the header — used by the chat, which manages its
|
|
15
|
+
own scrolling and needs the full height rather than a list of options. */
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
/** Kept mounted but out of sight, so a panel holding state (the chat's
|
|
18
|
+
conversation) survives a trip to another section. */
|
|
19
|
+
hidden?: boolean;
|
|
14
20
|
options?: SubPanelOption[];
|
|
15
21
|
cta?: string;
|
|
16
22
|
emptyState?: ReactNode;
|
|
@@ -19,5 +25,5 @@ interface ToolbarSubPanelProps {
|
|
|
19
25
|
onBack?: () => void;
|
|
20
26
|
headerExtras?: ReactNode;
|
|
21
27
|
}
|
|
22
|
-
declare const ToolbarSubPanel: ({ title, options, cta, emptyState, catalog, catalogPage, onBack, headerExtras, }: ToolbarSubPanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
declare const ToolbarSubPanel: ({ title, children, hidden, options, cta, emptyState, catalog, catalogPage, onBack, headerExtras, }: ToolbarSubPanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
23
29
|
export default ToolbarSubPanel;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
interface ZoomControlsProps {
|
|
2
|
-
|
|
2
|
+
subPanel?: "none" | "standard" | "ai";
|
|
3
3
|
}
|
|
4
|
-
declare const ZoomControls: ({
|
|
4
|
+
declare const ZoomControls: ({ subPanel }: ZoomControlsProps) => import("react/jsx-runtime").JSX.Element;
|
|
5
5
|
export default ZoomControls;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactPlannerStore } from "../../store/ReactPlannerStore";
|
|
2
|
+
/**
|
|
3
|
+
* Did anything the 2D viewer paints change — and nothing that doesn't?
|
|
4
|
+
*
|
|
5
|
+
* Scene identity is too blunt to drive this. Selection is drawn, so it changes
|
|
6
|
+
* the pixels and the byte-comparison downstream won't filter it either: left
|
|
7
|
+
* alone, idly clicking around a finished plan re-uploads a thumbnail per click,
|
|
8
|
+
* each with a different element highlighted. Geometry, layer visibility, the
|
|
9
|
+
* viewport box and the view options that live outside the planner record are
|
|
10
|
+
* what count.
|
|
11
|
+
*/
|
|
12
|
+
export declare function hasVisualChange(next: ReactPlannerStore, prev: ReactPlannerStore): boolean;
|
package/package.json
CHANGED