@netless/fastboard-react 0.2.6 → 0.2.9
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.js +3281 -342
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3245 -306
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -15
- package/src/components/Fastboard.scss +6 -1
- package/src/components/Fastboard.tsx +3 -2
- package/src/components/PageControl/PageControl.scss +15 -11
- package/src/components/PageControl/PageControl.tsx +15 -17
- package/src/components/PlayerControl/PlayerControl.scss +13 -12
- package/src/components/PlayerControl/PlayerControl.tsx +3 -3
- package/src/components/RedoUndo/RedoUndo.scss +10 -10
- package/src/components/ReplayFastboard.tsx +36 -0
- package/src/components/Toolbar/Toolbar.scss +54 -15
- package/src/components/Toolbar/components/AppsButton.tsx +41 -8
- package/src/components/Toolbar/components/ShapesButton.tsx +11 -3
- package/src/components/Toolbar/hooks.ts +9 -0
- package/src/components/Toolbar/icons/Laser.tsx +21 -0
- package/src/components/Toolbar/icons/Loading.tsx +13 -0
- package/src/components/ZoomControl/ZoomControl.scss +15 -11
- package/src/components/ZoomControl/ZoomControl.tsx +4 -2
- package/src/components/tippy-util.ts +18 -0
- package/src/icons/Left.tsx +15 -0
- package/src/icons/Minus.tsx +2 -2
- package/src/icons/Plus.tsx +2 -2
- package/src/icons/Redo.tsx +6 -5
- package/src/icons/Reset.tsx +4 -6
- package/src/icons/Right.tsx +15 -0
- package/src/icons/Undo.tsx +6 -5
- package/src/icons/WhiteboardAdd.tsx +26 -0
- package/src/index.ts +1 -0
- package/src/vanilla/index.tsx +14 -4
- package/src/icons/ChevronLeft.tsx +0 -21
- package/src/icons/ChevronRight.tsx +0 -21
- package/src/icons/FilePlus.tsx +0 -18
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ApplianceNames, Color, MemberState, ShapeType } from "white-web-sdk";
|
|
2
|
+
import type { AppsStatus } from "@netless/fastboard-core";
|
|
2
3
|
|
|
3
4
|
import { useCallback, useState } from "react";
|
|
4
5
|
|
|
@@ -12,6 +13,7 @@ export interface ToolbarHook {
|
|
|
12
13
|
readonly writable: boolean;
|
|
13
14
|
readonly memberState: MemberState | undefined;
|
|
14
15
|
readonly lastShape: UnifiedShape;
|
|
16
|
+
readonly appsStatus: AppsStatus;
|
|
15
17
|
cleanCurrentScene(): void;
|
|
16
18
|
setAppliance(appliance: ApplianceNames, shape?: ShapeType): void;
|
|
17
19
|
setStrokeWidth(width: number): void;
|
|
@@ -22,10 +24,15 @@ export function useRoomState() {
|
|
|
22
24
|
return useFastboardValue(useFastboardApp().memberState);
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
export function useAppsStatus() {
|
|
28
|
+
return useFastboardValue(useFastboardApp().appsStatus);
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
export function useToolbar(): ToolbarHook {
|
|
26
32
|
const app = useFastboardApp();
|
|
27
33
|
const writable = useWritable();
|
|
28
34
|
const memberState = useRoomState();
|
|
35
|
+
const appsStatus = useAppsStatus();
|
|
29
36
|
const [lastShape, setLastShape] = useState<UnifiedShape>("rectangle" as ApplianceNames.rectangle);
|
|
30
37
|
|
|
31
38
|
const cleanCurrentScene = useCallback(() => {
|
|
@@ -62,6 +69,7 @@ export function useToolbar(): ToolbarHook {
|
|
|
62
69
|
writable,
|
|
63
70
|
memberState,
|
|
64
71
|
lastShape,
|
|
72
|
+
appsStatus,
|
|
65
73
|
cleanCurrentScene,
|
|
66
74
|
setAppliance,
|
|
67
75
|
setStrokeWidth,
|
|
@@ -73,6 +81,7 @@ export const EmptyToolbarHook: ToolbarHook = {
|
|
|
73
81
|
writable: false,
|
|
74
82
|
memberState: undefined,
|
|
75
83
|
lastShape: "rectangle" as ApplianceNames.rectangle,
|
|
84
|
+
appsStatus: {},
|
|
76
85
|
cleanCurrentScene: noop,
|
|
77
86
|
setAppliance: noop,
|
|
78
87
|
setStrokeWidth: noop,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IconProps } from "../../../typings";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { getStroke } from "../../../theme";
|
|
5
|
+
|
|
6
|
+
export const Laser = (props: IconProps) => {
|
|
7
|
+
const stroke = getStroke(props);
|
|
8
|
+
return (
|
|
9
|
+
<svg viewBox="0 0 24 24">
|
|
10
|
+
<g fill="none" fill-rule="evenodd">
|
|
11
|
+
<circle cx="12" cy="12" r="2" fill={stroke} />
|
|
12
|
+
<path
|
|
13
|
+
stroke={stroke}
|
|
14
|
+
stroke-linecap="square"
|
|
15
|
+
stroke-linejoin="round"
|
|
16
|
+
d="M12 4v2m0 12v2m8-8h-2M6 12H4m13.657 5.657-1.414-1.414M7.757 7.757 6.343 6.343m0 11.314 1.414-1.414m8.486-8.486 1.414-1.414"
|
|
17
|
+
/>
|
|
18
|
+
</g>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IconProps } from "../../../typings";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { getStroke } from "../../../theme";
|
|
5
|
+
|
|
6
|
+
export const Loading = (props: IconProps) => {
|
|
7
|
+
const stroke = getStroke(props);
|
|
8
|
+
return (
|
|
9
|
+
<svg viewBox="0 0 24 24">
|
|
10
|
+
<path fill={stroke} d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8Z"></path>
|
|
11
|
+
</svg>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -7,19 +7,19 @@ $name: "fastboard-zoom-control";
|
|
|
7
7
|
gap: 4px;
|
|
8
8
|
padding: 4px;
|
|
9
9
|
border-radius: 4px;
|
|
10
|
-
backdrop-filter: blur(
|
|
11
|
-
-webkit-backdrop-filter: blur(
|
|
10
|
+
backdrop-filter: blur(5px);
|
|
11
|
+
-webkit-backdrop-filter: blur(5px);
|
|
12
12
|
|
|
13
13
|
&.light {
|
|
14
14
|
color: #333;
|
|
15
|
-
background-color: rgba(
|
|
16
|
-
border: 1px solid
|
|
15
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
16
|
+
border: 1px solid #e5e8f0;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
&.dark {
|
|
20
20
|
color: #ddd;
|
|
21
|
-
background-color:
|
|
22
|
-
border: 1px solid
|
|
21
|
+
background-color: #14181e;
|
|
22
|
+
border: 1px solid #383b42;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -38,8 +38,8 @@ $name: "fastboard-zoom-control";
|
|
|
38
38
|
|
|
39
39
|
svg,
|
|
40
40
|
img {
|
|
41
|
-
width:
|
|
42
|
-
height:
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100%;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
&:disabled {
|
|
@@ -48,11 +48,11 @@ $name: "fastboard-zoom-control";
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
&.light:not(:disabled):hover {
|
|
51
|
-
background-color:
|
|
51
|
+
background-color: #ebf2ff;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
&.dark:not(:disabled):hover {
|
|
55
|
-
background-color:
|
|
55
|
+
background-color: #383b42;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -69,12 +69,16 @@ $name: "fastboard-zoom-control";
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
.#{$name}-text {
|
|
73
|
+
line-height: 24px;
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
.#{$name}-percent {
|
|
73
77
|
opacity: 0.6;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
.#{$name}-scale,
|
|
77
81
|
.#{$name}-percent {
|
|
78
|
-
font-size:
|
|
82
|
+
font-size: 14px;
|
|
79
83
|
font-variant-numeric: tabular-nums;
|
|
80
84
|
}
|
|
@@ -55,8 +55,10 @@ export function ZoomControl({
|
|
|
55
55
|
/>
|
|
56
56
|
</button>
|
|
57
57
|
</Tippy>
|
|
58
|
-
<span className={clsx(`${name}-
|
|
59
|
-
|
|
58
|
+
<span className={clsx(`${name}-text`, theme)}>
|
|
59
|
+
<span className={clsx(`${name}-scale`, theme)}>{Math.ceil(scale * 100)}</span>
|
|
60
|
+
<span className={clsx(`${name}-percent`, theme)}>%</span>
|
|
61
|
+
</span>
|
|
60
62
|
<Tippy
|
|
61
63
|
className="fastboard-tip"
|
|
62
64
|
content={t("zoomIn")}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Instance } from "tippy.js";
|
|
2
|
+
import tippy from "tippy.js";
|
|
3
|
+
|
|
4
|
+
export const instances = new Set<Instance>();
|
|
5
|
+
|
|
6
|
+
export function onCreate(instance: Instance) {
|
|
7
|
+
instances.add(instance);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function onDestroy(instance: Instance) {
|
|
11
|
+
instances.delete(instance);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
tippy.setDefaultProps({ onCreate, onDestroy });
|
|
15
|
+
|
|
16
|
+
export function hideAll() {
|
|
17
|
+
instances.forEach(instance => instance.hide());
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IconProps } from "../typings";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { themes } from "../theme";
|
|
5
|
+
|
|
6
|
+
export function Left({ theme = "light", active }: IconProps) {
|
|
7
|
+
const config = themes[theme];
|
|
8
|
+
const stroke = active ? config.activeColor : config.color;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none">
|
|
12
|
+
<path d="m14 8-2 2-2 2 2 2 2 2" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
package/src/icons/Minus.tsx
CHANGED
|
@@ -8,8 +8,8 @@ export function Minus({ theme = "light", active }: IconProps) {
|
|
|
8
8
|
const stroke = active ? config.activeColor : config.color;
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
|
-
<svg viewBox="0 0 24 24">
|
|
12
|
-
<path
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none">
|
|
12
|
+
<path d="M5 12h14" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
13
13
|
</svg>
|
|
14
14
|
);
|
|
15
15
|
}
|
package/src/icons/Plus.tsx
CHANGED
|
@@ -8,8 +8,8 @@ export function Plus({ theme = "light", active }: IconProps) {
|
|
|
8
8
|
const stroke = active ? config.activeColor : config.color;
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
|
-
<svg viewBox="0 0 24 24">
|
|
12
|
-
<path
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none">
|
|
12
|
+
<path d="M5 12h14m-7-7v14" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
13
13
|
</svg>
|
|
14
14
|
);
|
|
15
15
|
}
|
package/src/icons/Redo.tsx
CHANGED
|
@@ -8,11 +8,12 @@ export function Redo({ theme = "light", active }: IconProps) {
|
|
|
8
8
|
const stroke = active ? config.activeColor : config.color;
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
|
-
<svg viewBox="0 0 24 24">
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none">
|
|
12
|
+
<path
|
|
13
|
+
d="M19 9.625H9v-1.25h10v1.25ZM5.625 13v6h-1.25v-6h1.25ZM9 9.625A3.375 3.375 0 0 0 5.625 13h-1.25A4.625 4.625 0 0 1 9 8.375v1.25Z"
|
|
14
|
+
fill={stroke}
|
|
15
|
+
/>
|
|
16
|
+
<path d="m15 5 4 4-4 4" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
16
17
|
</svg>
|
|
17
18
|
);
|
|
18
19
|
}
|
package/src/icons/Reset.tsx
CHANGED
|
@@ -8,12 +8,10 @@ export function Reset({ theme = "light", active }: IconProps) {
|
|
|
8
8
|
const stroke = active ? config.activeColor : config.color;
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
|
-
<svg viewBox="0 0 24 24">
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<circle cx="188" cy="696" r="1" fill={stroke} />
|
|
16
|
-
</g>
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none">
|
|
12
|
+
<circle cx="12" cy="12" fill={stroke} r="2" />
|
|
13
|
+
<path d="M12 3v4m0 10v4m9-9h-4M7 12H3" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
14
|
+
<circle cx="12" cy="12" r="7" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
17
15
|
</svg>
|
|
18
16
|
);
|
|
19
17
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IconProps } from "../typings";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { themes } from "../theme";
|
|
5
|
+
|
|
6
|
+
export function Right({ theme = "light", active }: IconProps) {
|
|
7
|
+
const config = themes[theme];
|
|
8
|
+
const stroke = active ? config.activeColor : config.color;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none">
|
|
12
|
+
<path d="m10 8 2 2 2 2-2 2-2 2" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
package/src/icons/Undo.tsx
CHANGED
|
@@ -8,11 +8,12 @@ export function Undo({ theme = "light", active }: IconProps) {
|
|
|
8
8
|
const stroke = active ? config.activeColor : config.color;
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
|
-
<svg viewBox="0 0 24 24">
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none">
|
|
12
|
+
<path
|
|
13
|
+
d="M5 9.625h10v-1.25H5v1.25ZM18.375 13v6h1.25v-6h-1.25ZM15 9.625A3.375 3.375 0 0 1 18.375 13h1.25A4.625 4.625 0 0 0 15 8.375v1.25Z"
|
|
14
|
+
fill={stroke}
|
|
15
|
+
/>
|
|
16
|
+
<path d="M9 5 5 9l4 4" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
16
17
|
</svg>
|
|
17
18
|
);
|
|
18
19
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IconProps } from "../typings";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { themes } from "../theme";
|
|
5
|
+
|
|
6
|
+
export function WhiteboardAdd({ theme = "light", active }: IconProps) {
|
|
7
|
+
const config = themes[theme];
|
|
8
|
+
const stroke = active ? config.activeColor : config.color;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none">
|
|
12
|
+
<path d="M4 20h16M4 6h16" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
13
|
+
<rect
|
|
14
|
+
height="10"
|
|
15
|
+
rx="1"
|
|
16
|
+
stroke={stroke}
|
|
17
|
+
strokeLinejoin="round"
|
|
18
|
+
strokeWidth="1.25"
|
|
19
|
+
width="14"
|
|
20
|
+
x="5"
|
|
21
|
+
y="8"
|
|
22
|
+
/>
|
|
23
|
+
<path d="M12 4v2m-3 7h6m-3-3v6" stroke={stroke} strokeLinejoin="round" strokeWidth="1.25" />
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
}
|
package/src/index.ts
CHANGED
package/src/vanilla/index.tsx
CHANGED
|
@@ -5,14 +5,24 @@ import React from "react";
|
|
|
5
5
|
import ReactDOM from "react-dom";
|
|
6
6
|
import { Fastboard } from "../components/Fastboard";
|
|
7
7
|
|
|
8
|
+
export type MountProps = Omit<FastboardProps & DivProps, "ref">;
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Mount fastboard app to some dom, returns the disposer, which will unmount the app.
|
|
10
12
|
* @example
|
|
11
13
|
* let app = await createFastboard({ ...config })
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* const { update, destroy } = mount(app, document.getElementById("whiteboard"))
|
|
15
|
+
* update({ theme: 'dark' })
|
|
16
|
+
* destroy()
|
|
14
17
|
*/
|
|
15
|
-
export function mount(app: FastboardApp, dom: HTMLElement, props:
|
|
18
|
+
export function mount(app: FastboardApp, dom: HTMLElement, props: MountProps) {
|
|
16
19
|
ReactDOM.render(<Fastboard app={app} {...props} />, dom);
|
|
17
|
-
return
|
|
20
|
+
return {
|
|
21
|
+
update(props: MountProps) {
|
|
22
|
+
ReactDOM.render(<Fastboard app={app} {...props} />, dom);
|
|
23
|
+
},
|
|
24
|
+
destroy() {
|
|
25
|
+
ReactDOM.unmountComponentAtNode(dom);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
18
28
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { IconProps } from "../typings";
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { themes } from "../theme";
|
|
5
|
-
|
|
6
|
-
export function ChevronLeft({ theme = "light", active }: IconProps) {
|
|
7
|
-
const config = themes[theme];
|
|
8
|
-
const stroke = active ? config.activeColor : config.color;
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<svg viewBox="0 0 24 24">
|
|
12
|
-
<path
|
|
13
|
-
fill="none"
|
|
14
|
-
stroke={stroke}
|
|
15
|
-
strokeLinecap="round"
|
|
16
|
-
strokeLinejoin="round"
|
|
17
|
-
d="m14 16-2-2-2-2 2-2 2-2"
|
|
18
|
-
/>
|
|
19
|
-
</svg>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { IconProps } from "../typings";
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { themes } from "../theme";
|
|
5
|
-
|
|
6
|
-
export function ChevronRight({ theme = "light", active }: IconProps) {
|
|
7
|
-
const config = themes[theme];
|
|
8
|
-
const stroke = active ? config.activeColor : config.color;
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<svg viewBox="0 0 24 24">
|
|
12
|
-
<path
|
|
13
|
-
fill="none"
|
|
14
|
-
stroke={stroke}
|
|
15
|
-
strokeLinecap="round"
|
|
16
|
-
strokeLinejoin="round"
|
|
17
|
-
d="m10 16 2-2 2-2-2-2-2-2"
|
|
18
|
-
/>
|
|
19
|
-
</svg>
|
|
20
|
-
);
|
|
21
|
-
}
|
package/src/icons/FilePlus.tsx
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { IconProps } from "../typings";
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { themes } from "../theme";
|
|
5
|
-
|
|
6
|
-
export function FilePlus({ theme = "light", active }: IconProps) {
|
|
7
|
-
const config = themes[theme];
|
|
8
|
-
const stroke = active ? config.activeColor : config.color;
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<svg viewBox="0 0 24 24">
|
|
12
|
-
<path
|
|
13
|
-
fill={stroke}
|
|
14
|
-
d="M12 7.5a.5.5 0 0 1 .09.992L12 8.5H8a1.5 1.5 0 0 0-1.493 1.356L6.5 10v6a1.5 1.5 0 0 0 1.356 1.493L8 17.5h6a1.5 1.5 0 0 0 1.493-1.356L15.5 16v-4a.5.5 0 0 1 .992-.09l.008.09v4a2.5 2.5 0 0 1-2.336 2.495L14 18.5H8a2.5 2.5 0 0 1-2.495-2.336L5.5 16v-6a2.5 2.5 0 0 1 2.336-2.495L8 7.5h4Zm4-2a.5.5 0 0 1 .492.41L16.5 6v1.5H18a.5.5 0 0 1 .09.992L18 8.5h-1.5V10a.5.5 0 0 1-.992.09L15.5 10V8.5H14a.5.5 0 0 1-.09-.992L14 7.5h1.5V6a.5.5 0 0 1 .5-.5Z"
|
|
15
|
-
/>
|
|
16
|
-
</svg>
|
|
17
|
-
);
|
|
18
|
-
}
|