@cnvx/nodal 0.3.3 → 0.5.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/BaseEdge.svelte +3 -3
- package/dist/BaseEdge.svelte.d.ts +1 -1
- package/dist/Surface.svelte +64 -25
- package/dist/Surface.svelte.d.ts +13 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/layout-utils.d.ts +31 -0
- package/dist/layout-utils.js +44 -0
- package/dist/svelte-actions.d.ts +21 -2
- package/dist/svelte-actions.js +66 -38
- package/package.json +1 -1
package/dist/BaseEdge.svelte
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
export interface NodalEdgeProps {
|
|
5
5
|
edge: SurfaceEdge;
|
|
6
|
-
|
|
6
|
+
d: string;
|
|
7
7
|
}
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
<script lang="ts">
|
|
11
|
-
const { edge,
|
|
11
|
+
const { edge, d }: NodalEdgeProps = $props();
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<path
|
|
15
|
-
d
|
|
15
|
+
{d}
|
|
16
16
|
fill="none"
|
|
17
17
|
stroke-width="2"
|
|
18
18
|
stroke="currentColor"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SurfaceEdge } from "./Surface.svelte";
|
|
2
2
|
export interface NodalEdgeProps {
|
|
3
3
|
edge: SurfaceEdge;
|
|
4
|
-
|
|
4
|
+
d: string;
|
|
5
5
|
}
|
|
6
6
|
declare const BaseEdge: import("svelte").Component<NodalEdgeProps, {}, "">;
|
|
7
7
|
type BaseEdge = ReturnType<typeof BaseEdge>;
|
package/dist/Surface.svelte
CHANGED
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
} from "./diagram-lib.js";
|
|
13
13
|
import type { HTMLAttributes } from "svelte/elements";
|
|
14
14
|
import type { Writable } from "svelte/store";
|
|
15
|
+
import {
|
|
16
|
+
getNodeAnchorFast,
|
|
17
|
+
getNodeAnchorWithBoundingClientRect,
|
|
18
|
+
} from "./layout-utils.js";
|
|
19
|
+
import { fade } from "svelte/transition";
|
|
20
|
+
import type { ComponentProps } from "svelte";
|
|
15
21
|
|
|
16
22
|
type PathGenParams =
|
|
17
23
|
| {
|
|
@@ -25,32 +31,52 @@
|
|
|
25
31
|
center?: Vector2;
|
|
26
32
|
};
|
|
27
33
|
|
|
28
|
-
export type SurfaceEdgeParams =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
export type SurfaceEdgeParams<C extends typeof BaseEdge = typeof BaseEdge> =
|
|
35
|
+
{
|
|
36
|
+
component?: C | [C, Omit<ComponentProps<C>, "d" | "edge">];
|
|
37
|
+
sourceAnchor?: Vector2;
|
|
38
|
+
targetAnchor?: Vector2;
|
|
39
|
+
svgPathAttributes?: HTMLAttributes<SVGPathElement>;
|
|
40
|
+
} & PathGenParams;
|
|
41
|
+
|
|
42
|
+
// export type SurfaceEdge = {
|
|
43
|
+
|
|
44
|
+
// } & SurfaceEdgeParams;
|
|
45
|
+
|
|
46
|
+
export interface PrecalculatedEdge {
|
|
47
|
+
x1: number;
|
|
48
|
+
y1: number;
|
|
49
|
+
x2: number;
|
|
50
|
+
y2: number;
|
|
51
|
+
}
|
|
34
52
|
|
|
35
53
|
export type SurfaceEdge = {
|
|
36
54
|
source: string | HTMLElement | string[] | HTMLElement[];
|
|
37
55
|
target: string | HTMLElement | string[] | HTMLElement[];
|
|
56
|
+
|
|
57
|
+
precalculated?: PrecalculatedEdge;
|
|
38
58
|
} & SurfaceEdgeParams;
|
|
39
59
|
</script>
|
|
40
60
|
|
|
41
61
|
<script lang="ts">
|
|
62
|
+
let svg: SVGSVGElement | undefined = $state();
|
|
63
|
+
|
|
42
64
|
const {
|
|
43
65
|
hostElement,
|
|
44
66
|
edges: _edges,
|
|
45
67
|
svgAttributes,
|
|
46
68
|
width: _width,
|
|
47
69
|
height: _height,
|
|
70
|
+
fadeInDuration = 80,
|
|
71
|
+
getNodeAnchor = getNodeAnchorWithBoundingClientRect,
|
|
48
72
|
}: {
|
|
49
73
|
hostElement: Element;
|
|
50
74
|
edges: SvelteMap<string, SurfaceEdge>;
|
|
51
75
|
width?: Writable<number>;
|
|
52
76
|
height?: Writable<number>;
|
|
53
77
|
svgAttributes?: HTMLAttributes<SVGElement>;
|
|
78
|
+
getNodeAnchor?: typeof getNodeAnchorFast;
|
|
79
|
+
fadeInDuration?: number;
|
|
54
80
|
} = $props();
|
|
55
81
|
|
|
56
82
|
export const width = _width;
|
|
@@ -158,6 +184,18 @@
|
|
|
158
184
|
}
|
|
159
185
|
|
|
160
186
|
function generateEdgePath(edge: SurfaceEdge) {
|
|
187
|
+
if (!svg) return [];
|
|
188
|
+
|
|
189
|
+
if (edge.precalculated) {
|
|
190
|
+
return generateCurvePath(
|
|
191
|
+
edge.precalculated.x1,
|
|
192
|
+
edge.precalculated.y1,
|
|
193
|
+
edge.precalculated.x2,
|
|
194
|
+
edge.precalculated.y2,
|
|
195
|
+
edge,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
161
199
|
const sourceNodes = getNodes(edge.source);
|
|
162
200
|
const targetNodes = getNodes(edge.target);
|
|
163
201
|
|
|
@@ -175,10 +213,12 @@
|
|
|
175
213
|
const sourceAnchor = getNodeAnchor(
|
|
176
214
|
source,
|
|
177
215
|
edge.sourceAnchor ?? Anchor.CENTER_CENTER,
|
|
216
|
+
svg!,
|
|
178
217
|
);
|
|
179
218
|
const targetAnchor = getNodeAnchor(
|
|
180
219
|
target,
|
|
181
220
|
edge.targetAnchor ?? Anchor.CENTER_CENTER,
|
|
221
|
+
svg!,
|
|
182
222
|
);
|
|
183
223
|
paths.push(
|
|
184
224
|
generateCurvePath(
|
|
@@ -203,29 +243,28 @@
|
|
|
203
243
|
|
|
204
244
|
return paths;
|
|
205
245
|
}
|
|
206
|
-
|
|
207
|
-
function getNodeAnchor(node: HTMLElement, anchor: Vector2) {
|
|
208
|
-
return {
|
|
209
|
-
left: node.offsetLeft + anchor.x * node.clientWidth,
|
|
210
|
-
top: node.offsetTop + anchor.y * node.clientHeight,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
246
|
</script>
|
|
214
247
|
|
|
248
|
+
<!-- style="position:absolute;top:0;right:0;bottom:0;left:0;height:100%;width:100%;overflow:visible;z-index:0;" -->
|
|
215
249
|
<svg
|
|
250
|
+
bind:this={svg}
|
|
216
251
|
preserveAspectRatio="none"
|
|
217
|
-
style="position:absolute;
|
|
252
|
+
style="position: absolute; inset: 0px; height: 1px; width: 1px; overflow: visible; z-index: 0;"
|
|
218
253
|
{...svgAttributes}
|
|
219
254
|
>
|
|
220
|
-
{#
|
|
221
|
-
{
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
255
|
+
{#if svg}
|
|
256
|
+
<g in:fade={{ duration: fadeInDuration }}>
|
|
257
|
+
{#each edges.values() as edge}
|
|
258
|
+
{#each generateEdgePath(edge) as d}
|
|
259
|
+
{@const EdgeComponent = edge.component ?? BaseEdge}
|
|
260
|
+
{#if Array.isArray(EdgeComponent)}
|
|
261
|
+
{@const [EdgeComp, edgeProps] = EdgeComponent}
|
|
262
|
+
<EdgeComp {d} {edge} {...edgeProps} />
|
|
263
|
+
{:else}
|
|
264
|
+
<EdgeComponent {d} {edge} />
|
|
265
|
+
{/if}
|
|
266
|
+
{/each}
|
|
267
|
+
{/each}
|
|
268
|
+
</g>
|
|
269
|
+
{/if}
|
|
231
270
|
</svg>
|
package/dist/Surface.svelte.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { SvelteMap } from "svelte/reactivity";
|
|
|
3
3
|
import { type Vector2 } from "./diagram-lib.js";
|
|
4
4
|
import type { HTMLAttributes } from "svelte/elements";
|
|
5
5
|
import type { Writable } from "svelte/store";
|
|
6
|
+
import { getNodeAnchorFast } from "./layout-utils.js";
|
|
7
|
+
import type { ComponentProps } from "svelte";
|
|
6
8
|
type PathGenParams = {
|
|
7
9
|
pathGen?: "bezier";
|
|
8
10
|
curvature?: number;
|
|
@@ -12,15 +14,22 @@ type PathGenParams = {
|
|
|
12
14
|
borderRadius?: number;
|
|
13
15
|
center?: Vector2;
|
|
14
16
|
};
|
|
15
|
-
export type SurfaceEdgeParams = {
|
|
16
|
-
component?:
|
|
17
|
+
export type SurfaceEdgeParams<C extends typeof BaseEdge = typeof BaseEdge> = {
|
|
18
|
+
component?: C | [C, Omit<ComponentProps<C>, "d" | "edge">];
|
|
17
19
|
sourceAnchor?: Vector2;
|
|
18
20
|
targetAnchor?: Vector2;
|
|
19
21
|
svgPathAttributes?: HTMLAttributes<SVGPathElement>;
|
|
20
22
|
} & PathGenParams;
|
|
23
|
+
export interface PrecalculatedEdge {
|
|
24
|
+
x1: number;
|
|
25
|
+
y1: number;
|
|
26
|
+
x2: number;
|
|
27
|
+
y2: number;
|
|
28
|
+
}
|
|
21
29
|
export type SurfaceEdge = {
|
|
22
30
|
source: string | HTMLElement | string[] | HTMLElement[];
|
|
23
31
|
target: string | HTMLElement | string[] | HTMLElement[];
|
|
32
|
+
precalculated?: PrecalculatedEdge;
|
|
24
33
|
} & SurfaceEdgeParams;
|
|
25
34
|
type $$ComponentProps = {
|
|
26
35
|
hostElement: Element;
|
|
@@ -28,6 +37,8 @@ type $$ComponentProps = {
|
|
|
28
37
|
width?: Writable<number>;
|
|
29
38
|
height?: Writable<number>;
|
|
30
39
|
svgAttributes?: HTMLAttributes<SVGElement>;
|
|
40
|
+
getNodeAnchor?: typeof getNodeAnchorFast;
|
|
41
|
+
fadeInDuration?: number;
|
|
31
42
|
};
|
|
32
43
|
declare const Surface: import("svelte").Component<$$ComponentProps, {
|
|
33
44
|
width: Writable<number> | undefined;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Vector2 } from "./diagram-lib.js";
|
|
2
|
+
export declare function getElementQuad(el: Element): DOMQuad | {
|
|
3
|
+
p1: {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
};
|
|
7
|
+
p2: {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
p3: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
p4: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare function toSvgPoint(svg: SVGGraphicsElement, x: number, y: number): {
|
|
21
|
+
left: number;
|
|
22
|
+
top: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function getNodeAnchorWithBoundingClientRect(el: Element, anchor: Vector2, svg: SVGGraphicsElement): {
|
|
25
|
+
left: number;
|
|
26
|
+
top: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function getNodeAnchorFast(node: HTMLElement, anchor: Vector2, svgElement: SVGGraphicsElement): {
|
|
29
|
+
left: number;
|
|
30
|
+
top: number;
|
|
31
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function getElementQuad(el) {
|
|
2
|
+
if (el.getBoxQuads) {
|
|
3
|
+
const quads = el.getBoxQuads();
|
|
4
|
+
if (quads && quads.length)
|
|
5
|
+
return quads[0];
|
|
6
|
+
}
|
|
7
|
+
// Fallback to rect -> quad
|
|
8
|
+
const r = el.getBoundingClientRect();
|
|
9
|
+
return {
|
|
10
|
+
p1: { x: r.left, y: r.top },
|
|
11
|
+
p2: { x: r.right, y: r.top },
|
|
12
|
+
p3: { x: r.right, y: r.bottom },
|
|
13
|
+
p4: { x: r.left, y: r.bottom },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function toSvgPoint(svg, x, y) {
|
|
17
|
+
const ctm = svg.getScreenCTM();
|
|
18
|
+
if (!ctm)
|
|
19
|
+
return { left: x, top: y };
|
|
20
|
+
const inv = ctm.inverse();
|
|
21
|
+
const p = new DOMPoint(x, y).matrixTransform(inv);
|
|
22
|
+
return { left: p.x, top: p.y };
|
|
23
|
+
}
|
|
24
|
+
export function getNodeAnchorWithBoundingClientRect(el, anchor, svg) {
|
|
25
|
+
const q = getElementQuad(el);
|
|
26
|
+
// Clamp to [0,1] to avoid surprises.
|
|
27
|
+
const ax = Math.max(0, Math.min(1, anchor.x));
|
|
28
|
+
const ay = Math.max(0, Math.min(1, anchor.y));
|
|
29
|
+
// Bilinear interpolation across the quad:
|
|
30
|
+
// p(ax,ay) = (1-ax)(1-ay) * p1 + ax(1-ay) * p2 + ax*ay * p3 + (1-ax)ay * p4
|
|
31
|
+
const w1 = (1 - ax) * (1 - ay);
|
|
32
|
+
const w2 = ax * (1 - ay);
|
|
33
|
+
const w3 = ax * ay;
|
|
34
|
+
const w4 = (1 - ax) * ay;
|
|
35
|
+
const sx = w1 * q.p1.x + w2 * q.p2.x + w3 * q.p3.x + w4 * q.p4.x;
|
|
36
|
+
const sy = w1 * q.p1.y + w2 * q.p2.y + w3 * q.p3.y + w4 * q.p4.y;
|
|
37
|
+
return toSvgPoint(svg, sx, sy);
|
|
38
|
+
}
|
|
39
|
+
export function getNodeAnchorFast(node, anchor, svgElement) {
|
|
40
|
+
return {
|
|
41
|
+
left: node.offsetLeft + anchor.x * node.clientWidth,
|
|
42
|
+
top: node.offsetTop + anchor.y * node.clientHeight,
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/svelte-actions.d.ts
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
import type { Attachment } from "svelte/attachments";
|
|
2
2
|
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
-
import
|
|
3
|
+
import { SvelteMap } from "svelte/reactivity";
|
|
4
|
+
import { type Writable } from "svelte/store";
|
|
5
|
+
import type { SurfaceEdge, SurfaceEdgeParams } from "./Surface.svelte";
|
|
6
|
+
import type { Vector2 } from "./diagram-lib.js";
|
|
4
7
|
type IndividualConnectActionParam = string | (SurfaceEdgeParams & {
|
|
5
8
|
target: string | string[];
|
|
6
9
|
}) | (SurfaceEdgeParams & {
|
|
7
10
|
source: string | string[];
|
|
8
11
|
});
|
|
9
|
-
|
|
12
|
+
interface NodalSurfaceElement extends HTMLElement {
|
|
13
|
+
_nodalSurface: {
|
|
14
|
+
internals: {
|
|
15
|
+
width: Writable<number> | undefined;
|
|
16
|
+
height: Writable<number> | undefined;
|
|
17
|
+
edges: SvelteMap<string, SurfaceEdge>;
|
|
18
|
+
};
|
|
19
|
+
schedule: () => void;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare function createNodal({ svgAttributes, getNodeAnchor, fadeInDuration, onMount, }?: Partial<{
|
|
10
23
|
svgAttributes: HTMLAttributes<SVGElement>;
|
|
24
|
+
onMount: (engine: NodalSurfaceElement["_nodalSurface"]) => void;
|
|
25
|
+
getNodeAnchor: (node: HTMLElement, anchor: Vector2, svgElement: SVGElement) => {
|
|
26
|
+
top: number;
|
|
27
|
+
left: number;
|
|
28
|
+
};
|
|
29
|
+
fadeInDuration: number;
|
|
11
30
|
}>): Attachment;
|
|
12
31
|
export declare function connectTo(...edges: IndividualConnectActionParam[]): Attachment;
|
|
13
32
|
export {};
|
package/dist/svelte-actions.js
CHANGED
|
@@ -3,38 +3,36 @@ import { SvelteMap } from "svelte/reactivity";
|
|
|
3
3
|
import { writable } from "svelte/store";
|
|
4
4
|
import Surface from "./Surface.svelte";
|
|
5
5
|
function drawCall(surface) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
// console.debug("Drawing nodal surface edges...");
|
|
7
|
+
const connections = surface.querySelectorAll("[data-nodal-connected='true']");
|
|
8
|
+
connections.forEach((conn) => {
|
|
9
|
+
// console.debug("Processing connection for:", conn);
|
|
10
|
+
const host = conn;
|
|
11
|
+
if (!conn._nodalEdgeConnections ||
|
|
12
|
+
conn._nodalEdgeConnections.length === 0) {
|
|
13
|
+
// console.debug("No edges found for host:", host);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
conn._nodalEdgeConnections.forEach((edge) => {
|
|
17
|
+
let edgeDef;
|
|
18
|
+
if (typeof edge == "string") {
|
|
19
|
+
edgeDef = {
|
|
20
|
+
source: host,
|
|
21
|
+
target: edge,
|
|
22
|
+
};
|
|
16
23
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
else {
|
|
26
|
-
edgeDef = {
|
|
27
|
-
source: "source" in edge ? edge.source : host,
|
|
28
|
-
target: "target" in edge ? edge.target : host,
|
|
29
|
-
...edge,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
surface._nodalSurface.edges.set(`${edgeDef.source}->${edgeDef.target}`, edgeDef);
|
|
33
|
-
});
|
|
24
|
+
else {
|
|
25
|
+
edgeDef = {
|
|
26
|
+
source: "source" in edge ? edge.source : host,
|
|
27
|
+
target: "target" in edge ? edge.target : host,
|
|
28
|
+
...edge,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
surface._nodalSurface.internals.edges.set(`${edgeDef.source}->${edgeDef.target}`, edgeDef);
|
|
34
32
|
});
|
|
35
33
|
});
|
|
36
34
|
}
|
|
37
|
-
export function createNodal({ svgAttributes = {}, } = {}) {
|
|
35
|
+
export function createNodal({ svgAttributes = {}, getNodeAnchor, fadeInDuration, onMount, } = {}) {
|
|
38
36
|
return (element) => {
|
|
39
37
|
console.debug("Creating nodal sruface..");
|
|
40
38
|
if (!Object.hasOwn(element, "_nodalSurface")) {
|
|
@@ -47,23 +45,53 @@ export function createNodal({ svgAttributes = {}, } = {}) {
|
|
|
47
45
|
width: writable(element.clientWidth),
|
|
48
46
|
height: writable(element.clientHeight),
|
|
49
47
|
svgAttributes,
|
|
48
|
+
getNodeAnchor,
|
|
49
|
+
fadeInDuration,
|
|
50
50
|
},
|
|
51
51
|
});
|
|
52
|
+
const engine = {
|
|
53
|
+
_isScheduled: false,
|
|
54
|
+
draw: () => drawCall(element),
|
|
55
|
+
schedule() {
|
|
56
|
+
if (engine._isScheduled)
|
|
57
|
+
return;
|
|
58
|
+
engine._isScheduled = true;
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
this._isScheduled = false;
|
|
61
|
+
drawCall(element);
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
internals: exports
|
|
65
|
+
};
|
|
52
66
|
Object.defineProperty(element, "_nodalSurface", {
|
|
53
|
-
value:
|
|
67
|
+
value: engine,
|
|
54
68
|
});
|
|
55
69
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
requestAnimationFrame(() => {
|
|
71
|
+
for (let entry of entries) {
|
|
72
|
+
const { width, height } = entry.contentRect;
|
|
73
|
+
// console.debug(
|
|
74
|
+
// "Resizing nodal surface to:",
|
|
75
|
+
// width,
|
|
76
|
+
// height,
|
|
77
|
+
// );
|
|
78
|
+
exports.width?.set(width);
|
|
79
|
+
exports.height?.set(height);
|
|
80
|
+
// redraw edges
|
|
81
|
+
// drawCall(element as NodalSurfaceElement);
|
|
82
|
+
engine.schedule();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
64
85
|
});
|
|
86
|
+
try {
|
|
87
|
+
onMount?.(engine);
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
console.error("Error in onMount callback for nodal surface:", e);
|
|
91
|
+
}
|
|
65
92
|
resizeObserver.observe(element);
|
|
66
|
-
|
|
93
|
+
engine.schedule();
|
|
94
|
+
// drawCall(element as NodalSurfaceElement);
|
|
67
95
|
}
|
|
68
96
|
};
|
|
69
97
|
}
|