@dailies-studio/shot-editor 0.1.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/dist/assets/index-B7notge7.css +1 -0
- package/dist/assets/index-BBE6Ta9L.js +52 -0
- package/dist/index.html +13 -0
- package/package.json +45 -0
- package/src/api.ts +289 -0
- package/src/app/App.tsx +394 -0
- package/src/audio/index.tsx +74 -0
- package/src/audio/preview.ts +364 -0
- package/src/bridge.ts +45 -0
- package/src/bridgeTypes.ts +582 -0
- package/src/channels.ts +40 -0
- package/src/main.tsx +15 -0
- package/src/panels/ResizeHandle.tsx +113 -0
- package/src/panels/agent/AgentPanel.tsx +478 -0
- package/src/panels/agent/agent.css +322 -0
- package/src/panels/export/ExportDialog.tsx +284 -0
- package/src/panels/files/FilePanel.tsx +564 -0
- package/src/panels/files/McpSection.tsx +66 -0
- package/src/panels/files/icons.tsx +118 -0
- package/src/panels/header/DocSwitcher.tsx +85 -0
- package/src/panels/header/Header.tsx +132 -0
- package/src/panels/header/HistoryControls.tsx +20 -0
- package/src/panels/header/StatusBadges.tsx +49 -0
- package/src/panels/header/TransportBar.tsx +86 -0
- package/src/panels/header/ZoomControls.tsx +38 -0
- package/src/panels/help/HelpSection.tsx +190 -0
- package/src/panels/inspector/FramingRows.tsx +69 -0
- package/src/panels/inspector/InspectorPanel.tsx +178 -0
- package/src/panels/inspector/MarkerSection.tsx +63 -0
- package/src/panels/inspector/ParamRow.tsx +71 -0
- package/src/panels/inspector/animate/BlockTiming.tsx +341 -0
- package/src/panels/inspector/animate/DuringBlocks.tsx +85 -0
- package/src/panels/inspector/animate/InOutPresets.tsx +57 -0
- package/src/panels/inspector/animate/ResolvedChannels.tsx +150 -0
- package/src/panels/inspector/animate/ShotSection.tsx +126 -0
- package/src/panels/inspector/animate/useBlocks.ts +43 -0
- package/src/panels/inspector/design/AlignRow.tsx +63 -0
- package/src/panels/inspector/design/AppearanceSection.tsx +49 -0
- package/src/panels/inspector/design/ClonerSection.tsx +142 -0
- package/src/panels/inspector/design/ComponentSection.tsx +94 -0
- package/src/panels/inspector/design/CornerSection.tsx +52 -0
- package/src/panels/inspector/design/FillSection.tsx +89 -0
- package/src/panels/inspector/design/HtmlSection.tsx +50 -0
- package/src/panels/inspector/design/InstanceSection.tsx +302 -0
- package/src/panels/inspector/design/MediaSection.tsx +251 -0
- package/src/panels/inspector/design/MeshSection.tsx +172 -0
- package/src/panels/inspector/design/ShadowSection.tsx +73 -0
- package/src/panels/inspector/design/StrokeSection.tsx +85 -0
- package/src/panels/inspector/design/TransformSection.tsx +91 -0
- package/src/panels/inspector/design/TypographySection.tsx +170 -0
- package/src/panels/inspector/scene/CameraSection.tsx +59 -0
- package/src/panels/inspector/scene/EffectsSection.tsx +123 -0
- package/src/panels/inspector/scene/SceneSection.tsx +71 -0
- package/src/panels/inspector/scene/TransitionSection.tsx +136 -0
- package/src/panels/inspector/useDocWrite.ts +268 -0
- package/src/panels/material/MaterialPicker.tsx +172 -0
- package/src/panels/stage/StageMenu.tsx +38 -0
- package/src/panels/stage/StagePanel.tsx +131 -0
- package/src/panels/timeline/AudioLane.tsx +340 -0
- package/src/panels/timeline/BlockRow.tsx +242 -0
- package/src/panels/timeline/ChannelRow.tsx +266 -0
- package/src/panels/timeline/LifespanRow.tsx +165 -0
- package/src/panels/timeline/MarkerLane.tsx +146 -0
- package/src/panels/timeline/SceneStrip.tsx +145 -0
- package/src/panels/timeline/TimelineDock.tsx +488 -0
- package/src/panels/timeline/model.ts +641 -0
- package/src/panels/timeline/snap.ts +191 -0
- package/src/panels/toolbar/AddMenu.tsx +230 -0
- package/src/panels/toolbar/CameraViewBar.tsx +70 -0
- package/src/panels/toolbar/ShadingBar.tsx +42 -0
- package/src/panels/toolbar/ToolDockBar.tsx +110 -0
- package/src/panels/toolbar/ViewModeBar.tsx +49 -0
- package/src/shotTokens.css +131 -0
- package/src/stage/FrustumGizmo.tsx +184 -0
- package/src/stage/GridPlane.tsx +274 -0
- package/src/stage/MoveGizmo.tsx +411 -0
- package/src/stage/PerfHud.tsx +206 -0
- package/src/stage/SplitRenderer.tsx +511 -0
- package/src/stage/StageApp.tsx +3423 -0
- package/src/stage/StageTools.tsx +581 -0
- package/src/stage/bridgeClient.ts +278 -0
- package/src/stage/feed.ts +243 -0
- package/src/stage/frustumGeometry.ts +172 -0
- package/src/stage/gizmoMath.ts +494 -0
- package/src/stage/handles.ts +151 -0
- package/src/stage/instanceModules.ts +152 -0
- package/src/stage/interactions/move.ts +172 -0
- package/src/stage/interactions/panZoom.ts +60 -0
- package/src/stage/isolation.ts +155 -0
- package/src/stage/main.tsx +110 -0
- package/src/stage/modalState.ts +427 -0
- package/src/stage/overlays/Overlays.tsx +1046 -0
- package/src/stage/panes.ts +121 -0
- package/src/stage/penState.ts +112 -0
- package/src/stage/project.ts +402 -0
- package/src/stage/shading.ts +55 -0
- package/src/stage/tokenColor.ts +26 -0
- package/src/stage/view.ts +182 -0
- package/src/stage/workingView.ts +636 -0
- package/src/state/agentActions.ts +203 -0
- package/src/state/clientId.ts +25 -0
- package/src/state/context.tsx +33 -0
- package/src/state/insert.ts +507 -0
- package/src/state/keymap.ts +560 -0
- package/src/state/menu.ts +141 -0
- package/src/state/panelSize.ts +26 -0
- package/src/state/renderEvents.ts +100 -0
- package/src/state/store.ts +4094 -0
- package/src/state/triangles.ts +70 -0
- package/src/state/types.ts +384 -0
- package/src/styles.css +2948 -0
- package/src/tools.ts +181 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Looking at one subtree, straight on.
|
|
2
|
+
//
|
|
3
|
+
// Isolation is an editor MODE, not a document state: nothing here is ever
|
|
4
|
+
// written to a Doc3D and nothing reaches an export. What it needs is a camera
|
|
5
|
+
// pose, and that pose is derived from the subtree itself rather than orbited to
|
|
6
|
+
// by hand — "enter this card and see it flat" has exactly one right answer.
|
|
7
|
+
//
|
|
8
|
+
// ── WHY NOT THE WORKING CAMERA ───────────────────────────────────────────
|
|
9
|
+
//
|
|
10
|
+
// The working pane's camera is a spherical orbit rig: its `up` is reset to
|
|
11
|
+
// world +y every frame (so it cannot roll) and its polar angle is clamped off
|
|
12
|
+
// both poles (so it cannot look straight down). A card rolled 30 degrees and a
|
|
13
|
+
// card lying flat are both perfectly ordinary, and neither can be framed by
|
|
14
|
+
// that rig. So the pose is built here from the subtree's own basis instead.
|
|
15
|
+
//
|
|
16
|
+
// ── AND WHY ORTHOGRAPHIC ─────────────────────────────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// Editing flat means no foreshortening: two children the same size have to
|
|
19
|
+
// measure the same on screen, and a nudge has to move the same number of pixels
|
|
20
|
+
// wherever it lands. A perspective camera at a derived distance is 1:1 only on
|
|
21
|
+
// the plane it was derived for — which is the plane the subtree's ROOT sits in,
|
|
22
|
+
// not the plane its children sit in once any of them has a z.
|
|
23
|
+
//
|
|
24
|
+
// The frustum is sized from the subtree's own bounds, measured in the ROOT's
|
|
25
|
+
// frame, so entering a subtree frames it whatever the scene camera was doing.
|
|
26
|
+
import { toLocal, worldStateAt } from "@dailies-studio/shot/doc";
|
|
27
|
+
import type { Mat3, Scene3D, Vec3 } from "@dailies-studio/shot/doc";
|
|
28
|
+
import type { Rational } from "@dailies-studio/engine";
|
|
29
|
+
|
|
30
|
+
/** How much of the pane is margin around the framed subtree, per side. */
|
|
31
|
+
export const ISOLATION_MARGIN = 0.12;
|
|
32
|
+
|
|
33
|
+
/** A subtree with no extent still needs a frustum — a single zero-size layer is
|
|
34
|
+
* a real document, and a zero-width ortho camera projects everything to NaN. */
|
|
35
|
+
const MIN_HALF_EXTENT = 1;
|
|
36
|
+
|
|
37
|
+
export interface IsolationFrame {
|
|
38
|
+
/** Where to look, in THREE world space. */
|
|
39
|
+
target: Vec3;
|
|
40
|
+
/** The subtree's own basis: column 2 is its normal, column 1 its up. */
|
|
41
|
+
rotation: Mat3;
|
|
42
|
+
/** Half-extents of the subtree's bounds IN ITS OWN FRAME, design px. */
|
|
43
|
+
halfWidth: number;
|
|
44
|
+
halfHeight: number;
|
|
45
|
+
/** Half-depth, for near/far. A flat subtree still gets a usable slab. */
|
|
46
|
+
halfDepth: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The pose and extent that frame `rootId`'s subtree head-on at a frame.
|
|
51
|
+
*
|
|
52
|
+
* The RESOLVED chain, not the authored one: an author entering a card whose
|
|
53
|
+
* parent is mid-rotation is looking at where it is now. Returns null when the
|
|
54
|
+
* layer is not in this scene, which is what "the isolation went stale" looks
|
|
55
|
+
* like from here.
|
|
56
|
+
*/
|
|
57
|
+
export function isolationFrame(
|
|
58
|
+
scene: Scene3D,
|
|
59
|
+
rootId: string,
|
|
60
|
+
subtree: ReadonlySet<string>,
|
|
61
|
+
sceneFrame: number,
|
|
62
|
+
fps: Rational,
|
|
63
|
+
): IsolationFrame | null {
|
|
64
|
+
const root = scene.layersById[rootId];
|
|
65
|
+
if (root === undefined) return null;
|
|
66
|
+
const rootPose = worldStateAt(scene, rootId, sceneFrame, fps);
|
|
67
|
+
|
|
68
|
+
let minX = Infinity;
|
|
69
|
+
let maxX = -Infinity;
|
|
70
|
+
let minY = Infinity;
|
|
71
|
+
let maxY = -Infinity;
|
|
72
|
+
let minZ = Infinity;
|
|
73
|
+
let maxZ = -Infinity;
|
|
74
|
+
for (const id of subtree) {
|
|
75
|
+
const layer = scene.layersById[id];
|
|
76
|
+
if (layer === undefined) continue;
|
|
77
|
+
// Each member's centre in the ROOT's frame, with its own axis-aligned box
|
|
78
|
+
// around it. The box ignores the member's own tilt and roll, the same
|
|
79
|
+
// simplification `groupLayers` makes for the same reason: a rotation-aware
|
|
80
|
+
// hull would frame a rolled child looser than the box the author sees.
|
|
81
|
+
const local = toLocal(rootPose, worldStateAt(scene, id, sceneFrame, fps));
|
|
82
|
+
const [w, h] = layer.transform.size;
|
|
83
|
+
minX = Math.min(minX, local.position[0] - w / 2);
|
|
84
|
+
maxX = Math.max(maxX, local.position[0] + w / 2);
|
|
85
|
+
minY = Math.min(minY, local.position[1] - h / 2);
|
|
86
|
+
maxY = Math.max(maxY, local.position[1] + h / 2);
|
|
87
|
+
minZ = Math.min(minZ, local.position[2]);
|
|
88
|
+
maxZ = Math.max(maxZ, local.position[2]);
|
|
89
|
+
}
|
|
90
|
+
if (!Number.isFinite(minX)) return null;
|
|
91
|
+
|
|
92
|
+
// The bounds' CENTRE, not the root's origin: a group whose children all sit
|
|
93
|
+
// to one side would otherwise be framed off-centre, which is the state the
|
|
94
|
+
// grouping arithmetic exists to make impossible to author but which a
|
|
95
|
+
// hand-written document can still hold.
|
|
96
|
+
const centre: Vec3 = [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2];
|
|
97
|
+
const world = worldOf(rootPose, centre);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
target: world,
|
|
101
|
+
rotation: rootPose.rotation,
|
|
102
|
+
halfWidth: Math.max(MIN_HALF_EXTENT, (maxX - minX) / 2),
|
|
103
|
+
halfHeight: Math.max(MIN_HALF_EXTENT, (maxY - minY) / 2),
|
|
104
|
+
halfDepth: Math.max(MIN_HALF_EXTENT, (maxZ - minZ) / 2),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** A point in the root's DESIGN frame, out in three world space. Design +y is
|
|
109
|
+
* down and +z is away, so both flip on the way through the one mapping. */
|
|
110
|
+
function worldOf(pose: { position: Vec3; rotation: Mat3 }, local: Vec3): Vec3 {
|
|
111
|
+
const d: Vec3 = [local[0], -local[1], -local[2]];
|
|
112
|
+
return [
|
|
113
|
+
pose.position[0] + pose.rotation[0][0] * d[0] + pose.rotation[0][1] * d[1] + pose.rotation[0][2] * d[2],
|
|
114
|
+
pose.position[1] + pose.rotation[1][0] * d[0] + pose.rotation[1][1] * d[1] + pose.rotation[1][2] * d[2],
|
|
115
|
+
pose.position[2] + pose.rotation[2][0] * d[0] + pose.rotation[2][1] * d[1] + pose.rotation[2][2] * d[2],
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The orthographic half-extents that show the whole subtree in a pane of this
|
|
121
|
+
* aspect, with a margin.
|
|
122
|
+
*
|
|
123
|
+
* Both axes take the SAME scale — the larger of the two requirements — because
|
|
124
|
+
* an ortho camera with different x and y scales is a squashed picture, and the
|
|
125
|
+
* point of isolation is that a square measures square.
|
|
126
|
+
*/
|
|
127
|
+
export function isolationExtent(
|
|
128
|
+
frame: IsolationFrame,
|
|
129
|
+
aspect: number,
|
|
130
|
+
): { halfWidth: number; halfHeight: number } {
|
|
131
|
+
const safe = Number.isFinite(aspect) && aspect > 0 ? aspect : 1;
|
|
132
|
+
const grow = 1 / (1 - 2 * ISOLATION_MARGIN);
|
|
133
|
+
const halfHeight = Math.max(frame.halfHeight, frame.halfWidth / safe) * grow;
|
|
134
|
+
return { halfWidth: halfHeight * safe, halfHeight };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Camera eye for an isolation frame: back along the subtree's own normal, far
|
|
138
|
+
* enough that the ortho slab clears the deepest member either way. */
|
|
139
|
+
export function isolationEye(frame: IsolationFrame): { eye: Vec3; up: Vec3; far: number } {
|
|
140
|
+
const normal: Vec3 = [frame.rotation[0][2], frame.rotation[1][2], frame.rotation[2][2]];
|
|
141
|
+
const up: Vec3 = [frame.rotation[0][1], frame.rotation[1][1], frame.rotation[2][1]];
|
|
142
|
+
// Distance is arbitrary for an orthographic projection, so it is chosen for
|
|
143
|
+
// the CLIP planes: far enough that nothing in the subtree is behind the eye,
|
|
144
|
+
// near enough that the depth range stays a sane number.
|
|
145
|
+
const back = frame.halfDepth * 4 + 1000;
|
|
146
|
+
return {
|
|
147
|
+
eye: [
|
|
148
|
+
frame.target[0] + normal[0] * back,
|
|
149
|
+
frame.target[1] + normal[1] * back,
|
|
150
|
+
frame.target[2] + normal[2] * back,
|
|
151
|
+
],
|
|
152
|
+
up,
|
|
153
|
+
far: back * 2 + frame.halfDepth * 4,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Stage entry — served ONLY through the project's Vite (/shot-stage.html +
|
|
2
|
+
// /@fs, package.json "./stage" export) so the three stack resolves to the
|
|
3
|
+
// STUDIO's pinned copies and the project's asset route is same-origin; never
|
|
4
|
+
// bundled into the chrome dist.
|
|
5
|
+
//
|
|
6
|
+
// INSTALL-BEFORE-PAINT (the canvas stage's posture): the chrome polls
|
|
7
|
+
// `iframe.contentWindow.__shotStudio` on load and must never observe a
|
|
8
|
+
// rendered-but-bridgeless stage, so the handle installs SYNCHRONOUSLY in
|
|
9
|
+
// `boot()`. StageApp itself renders on the chrome's `connect()` — there is
|
|
10
|
+
// nothing worth painting before a chrome exists, and the document arrives
|
|
11
|
+
// immediately after.
|
|
12
|
+
import { createRoot } from "react-dom/client";
|
|
13
|
+
import { StageAssetsContext, StudioSurfaceContext } from "@dailies-studio/runtime";
|
|
14
|
+
import type { StageAssetResolver } from "@dailies-studio/runtime";
|
|
15
|
+
// The stage is its OWN document and inherits nothing from the chrome's
|
|
16
|
+
// stylesheet, so it adopts the design tokens by importing them. tokens.css and
|
|
17
|
+
// NOT styles.css: the palette is inert until referenced, while the widget rules
|
|
18
|
+
// and the global box-sizing reset would repaint the author's own content
|
|
19
|
+
// differently here than in the export — which is exactly the preview != runtime
|
|
20
|
+
// split this whole lane refuses.
|
|
21
|
+
import "@dailies-studio/ui/tokens.css";
|
|
22
|
+
// Then THIS LANE's palette on top, because the overlays drawn over the stage
|
|
23
|
+
// read --accent, --guide and --frame-border, and the house values are not the
|
|
24
|
+
// ones the chrome around them is using. Without it the selection quad and the
|
|
25
|
+
// tilt handles paint in the house rose while every control beside them is
|
|
26
|
+
// blue — the overlays were never wrong, they were reading a token nobody had
|
|
27
|
+
// overridden for this document. Same file the chrome imports; one definition.
|
|
28
|
+
import "../shotTokens.css";
|
|
29
|
+
import { createStageClient } from "./bridgeClient.js";
|
|
30
|
+
import { StageApp } from "./StageApp.js";
|
|
31
|
+
|
|
32
|
+
// WITHOUT THIS PROVIDER, IMAGE AND VIDEO LAYERS ARE BLANK. `useStageAssetResolver`
|
|
33
|
+
// falls back to the bare content-addressed filename, which resolves against
|
|
34
|
+
// /shot-stage.html and 404s — the dev server serves the asset store only at
|
|
35
|
+
// /studio/assets/ (same origin here, since this iframe rides the project's
|
|
36
|
+
// Vite). The harness hit this exact failure class before us; mirror its
|
|
37
|
+
// resolver (cli/src/harnessRuntime.ts).
|
|
38
|
+
const stageAssets: StageAssetResolver = (storePath) =>
|
|
39
|
+
"/studio/assets/" + encodeURIComponent(storePath);
|
|
40
|
+
|
|
41
|
+
export function boot(): void {
|
|
42
|
+
if (window.__shotStudio) {
|
|
43
|
+
console.warn("[shot-editor stage] boot() called twice — ignoring");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
document.documentElement.style.height = "100%";
|
|
47
|
+
document.body.style.cssText = "margin:0;height:100%;overflow:hidden;background:#000";
|
|
48
|
+
const container = document.createElement("div");
|
|
49
|
+
container.style.cssText = "position:fixed;inset:0";
|
|
50
|
+
document.body.appendChild(container);
|
|
51
|
+
|
|
52
|
+
const client = createStageClient();
|
|
53
|
+
// Install BEFORE first paint, and before a render is even scheduled.
|
|
54
|
+
window.__shotStudio = client.bridge;
|
|
55
|
+
|
|
56
|
+
const root = createRoot(container);
|
|
57
|
+
client.setDisposer(() => {
|
|
58
|
+
root.unmount();
|
|
59
|
+
container.remove();
|
|
60
|
+
delete window.__shotStudio;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// First render on connect: `connect()` flips `connected`, so subscribing
|
|
64
|
+
// catches it whether the chrome connects on the next poll tick or already
|
|
65
|
+
// did between install and subscribe.
|
|
66
|
+
let rendered = false;
|
|
67
|
+
const renderOnConnect = (): void => {
|
|
68
|
+
if (rendered || !client.getState().connected) return;
|
|
69
|
+
rendered = true;
|
|
70
|
+
unsubscribe();
|
|
71
|
+
// THE STUDIO-SURFACE MARKER, on the ROOT and never on a branch (the
|
|
72
|
+
// harness's posture). This stage mounts the shot runtime with an explicit
|
|
73
|
+
// frame and no wall clock; anything that reads "no ambient clock" as "a
|
|
74
|
+
// real page" and arms an animation would be animating under the selection
|
|
75
|
+
// handles while a gesture reads live geometry off a moving target.
|
|
76
|
+
root.render(
|
|
77
|
+
<StudioSurfaceContext.Provider value={true}>
|
|
78
|
+
<StageAssetsContext.Provider value={stageAssets}>
|
|
79
|
+
<StageApp client={client} />
|
|
80
|
+
</StageAssetsContext.Provider>
|
|
81
|
+
</StudioSurfaceContext.Provider>,
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
const unsubscribe = client.subscribe(renderOnConnect);
|
|
85
|
+
renderOnConnect();
|
|
86
|
+
forwardShotDocChanged();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The server's "this shot changed under you" push, RELAYED to the chrome.
|
|
91
|
+
*
|
|
92
|
+
* THIS IFRAME IS THE ONLY HALF WITH AN HMR CLIENT. The server pushes over the
|
|
93
|
+
* Vite WS, but the chrome is served as a PREBUILT STATIC bundle from dist/ —
|
|
94
|
+
* Vite strips its `import.meta.hot` branch at build time, so a listener there
|
|
95
|
+
* would be dead in every shipped build. The stage is served through the
|
|
96
|
+
* project's Vite and has a live client, so it forwards: same-origin
|
|
97
|
+
* postMessage to the parent, which the chrome turns back into the event its
|
|
98
|
+
* handler already takes. Exactly the relay the canvas stage performs.
|
|
99
|
+
*/
|
|
100
|
+
function forwardShotDocChanged(): void {
|
|
101
|
+
const hot = typeof import.meta.hot?.on === "function" ? import.meta.hot : null;
|
|
102
|
+
if (hot === null || window.parent === window) return;
|
|
103
|
+
hot.on("studio:shot-doc-changed", (data: unknown) => {
|
|
104
|
+
try {
|
|
105
|
+
window.parent.postMessage({ type: "studio:shot-doc-changed", data }, location.origin);
|
|
106
|
+
} catch {
|
|
107
|
+
// A relay must never take the stage down.
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|