@wix/ditto-codegen-public 1.0.181 → 1.0.183
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/examples-apps/custom-element/countdown-widget/components/ColorPickerField.tsx +27 -0
- package/dist/examples-apps/custom-element/countdown-widget/components/FontPickerField.tsx +34 -0
- package/dist/examples-apps/custom-element/countdown-widget/components/Separator.tsx +10 -0
- package/dist/examples-apps/custom-element/countdown-widget/components/TimeBlock.tsx +23 -0
- package/dist/examples-apps/custom-element/countdown-widget/extensions.ts +18 -0
- package/dist/examples-apps/custom-element/countdown-widget/panel.tsx +146 -0
- package/dist/examples-apps/custom-element/countdown-widget/styles.ts +73 -0
- package/dist/examples-apps/custom-element/countdown-widget/utils.ts +46 -0
- package/dist/examples-apps/custom-element/countdown-widget/widget.tsx +97 -0
- package/dist/out.js +124 -19
- package/dist/wix-cli-templates/src/site/widgets/custom-elements/my-widget/panel.tsx +53 -0
- package/dist/wix-cli-templates/src/site/widgets/custom-elements/my-widget/widget.tsx +33 -4
- package/package.json +2 -2
- package/dist/examples-apps/custom-element/src/widgets/custom-elements/countdown-timer/widget.tsx +0 -493
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import { inputs } from '@wix/editor';
|
|
3
|
+
import { FormField, Box, FillPreview, SidePanel } from '@wix/design-system';
|
|
4
|
+
|
|
5
|
+
interface ColorPickerFieldProps {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string;
|
|
8
|
+
onChange: (value: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ColorPickerField: FC<ColorPickerFieldProps> = ({
|
|
12
|
+
label,
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
}) => (
|
|
16
|
+
<SidePanel.Field>
|
|
17
|
+
<FormField label={label}>
|
|
18
|
+
<Box width="30px" height="30px">
|
|
19
|
+
<FillPreview
|
|
20
|
+
fill={value}
|
|
21
|
+
onClick={() => inputs.selectColor(value, { onChange })}
|
|
22
|
+
/>
|
|
23
|
+
</Box>
|
|
24
|
+
</FormField>
|
|
25
|
+
</SidePanel.Field>
|
|
26
|
+
);
|
|
27
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
import { inputs } from '@wix/editor';
|
|
3
|
+
import { FormField, Button, Text, SidePanel } from '@wix/design-system';
|
|
4
|
+
|
|
5
|
+
interface FontValue {
|
|
6
|
+
font: string;
|
|
7
|
+
textDecoration: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface FontPickerFieldProps {
|
|
11
|
+
label: string;
|
|
12
|
+
value: FontValue;
|
|
13
|
+
onChange: (value: FontValue) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const FontPickerField: FC<FontPickerFieldProps> = ({
|
|
17
|
+
label,
|
|
18
|
+
value,
|
|
19
|
+
onChange,
|
|
20
|
+
}) => (
|
|
21
|
+
<SidePanel.Field>
|
|
22
|
+
<FormField label={label}>
|
|
23
|
+
<Button
|
|
24
|
+
size="small"
|
|
25
|
+
priority="secondary"
|
|
26
|
+
onClick={() => inputs.selectFont(value, { onChange })}
|
|
27
|
+
fullWidth
|
|
28
|
+
>
|
|
29
|
+
<Text size="small" ellipsis>Change Font</Text>
|
|
30
|
+
</Button>
|
|
31
|
+
</FormField>
|
|
32
|
+
</SidePanel.Field>
|
|
33
|
+
);
|
|
34
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { type FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface TimeBlockProps {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
numberStyle: React.CSSProperties;
|
|
7
|
+
labelStyle: React.CSSProperties;
|
|
8
|
+
blockStyle: React.CSSProperties;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const TimeBlock: FC<TimeBlockProps> = ({
|
|
12
|
+
value,
|
|
13
|
+
label,
|
|
14
|
+
numberStyle,
|
|
15
|
+
labelStyle,
|
|
16
|
+
blockStyle,
|
|
17
|
+
}) => (
|
|
18
|
+
<div style={blockStyle}>
|
|
19
|
+
<div style={numberStyle}>{value}</div>
|
|
20
|
+
<div style={labelStyle}>{label}</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { extensions } from '@wix/astro/builders';
|
|
2
|
+
export const sitewidgetcountdownWidget = extensions.customElement({
|
|
3
|
+
id: '54db089e-aa5b-436a-9dfa-074f2efad2ce',
|
|
4
|
+
name: 'Countdown Widget',
|
|
5
|
+
tagName: 'countdown-widget',
|
|
6
|
+
element: './site/widgets/custom-elements/countdown-widget/widget.tsx',
|
|
7
|
+
settings: './site/widgets/custom-elements/countdown-widget/panel.tsx',
|
|
8
|
+
installation: {
|
|
9
|
+
autoAdd: true
|
|
10
|
+
},
|
|
11
|
+
width: {
|
|
12
|
+
defaultWidth: 500,
|
|
13
|
+
allowStretch: true
|
|
14
|
+
},
|
|
15
|
+
height: {
|
|
16
|
+
defaultHeight: 500
|
|
17
|
+
}
|
|
18
|
+
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import React, { type FC, useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { widget } from "@wix/editor";
|
|
3
|
+
import {
|
|
4
|
+
SidePanel,
|
|
5
|
+
WixDesignSystemProvider,
|
|
6
|
+
Input,
|
|
7
|
+
FormField,
|
|
8
|
+
TimeInput,
|
|
9
|
+
Box,
|
|
10
|
+
} from "@wix/design-system";
|
|
11
|
+
import "@wix/design-system/styles.global.css";
|
|
12
|
+
import { ColorPickerField } from "./components/ColorPickerField";
|
|
13
|
+
import { FontPickerField } from "./components/FontPickerField";
|
|
14
|
+
import { parseTimeValue } from "./utils";
|
|
15
|
+
|
|
16
|
+
const DEFAULT_BG_COLOR = "#0a0e27";
|
|
17
|
+
const DEFAULT_TEXT_COLOR = "#00ff88";
|
|
18
|
+
const DEFAULT_TEXT_FONT = "";
|
|
19
|
+
const DEFAULT_TEXT_DECORATION = "";
|
|
20
|
+
|
|
21
|
+
const Panel: FC = () => {
|
|
22
|
+
const [title, setTitle] = useState<string>("Countdown");
|
|
23
|
+
const [targetDate, setTargetDate] = useState<string>("");
|
|
24
|
+
const [targetTime, setTargetTime] = useState<string>("00:00");
|
|
25
|
+
const [bgColor, setBgColor] = useState<string>(DEFAULT_BG_COLOR);
|
|
26
|
+
const [textColor, setTextColor] = useState<string>(DEFAULT_TEXT_COLOR);
|
|
27
|
+
const [font, setFont] = useState({ font: DEFAULT_TEXT_FONT, textDecoration: DEFAULT_TEXT_DECORATION });
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
Promise.all([
|
|
31
|
+
widget.getProp("title"),
|
|
32
|
+
widget.getProp("target-date"),
|
|
33
|
+
widget.getProp("target-time"),
|
|
34
|
+
widget.getProp("bg-color"),
|
|
35
|
+
widget.getProp("text-color"),
|
|
36
|
+
widget.getProp("font"),
|
|
37
|
+
])
|
|
38
|
+
.then(([titleVal, dateVal, timeVal, bgColorVal, textColorVal, fontString]) => {
|
|
39
|
+
setTitle(titleVal || "Countdown");
|
|
40
|
+
setTargetDate(dateVal || "");
|
|
41
|
+
setTargetTime(timeVal || "00:00");
|
|
42
|
+
setBgColor(bgColorVal || DEFAULT_BG_COLOR);
|
|
43
|
+
setTextColor(textColorVal || DEFAULT_TEXT_COLOR);
|
|
44
|
+
setFont(JSON.parse(fontString || "{}"));
|
|
45
|
+
})
|
|
46
|
+
.catch((error) => console.error("Failed to fetch widget properties:", error));
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const handleTitleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
50
|
+
const newTitle = event.target.value;
|
|
51
|
+
setTitle(newTitle);
|
|
52
|
+
widget.setProp("title", newTitle);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const handleDateChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
56
|
+
const newDate = event.target.value;
|
|
57
|
+
setTargetDate(newDate);
|
|
58
|
+
widget.setProp("target-date", newDate);
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const handleTimeChange = useCallback(({ date }: { date: Date }) => {
|
|
62
|
+
if (date) {
|
|
63
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
64
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
65
|
+
const newTime = `${hours}:${minutes}`;
|
|
66
|
+
setTargetTime(newTime);
|
|
67
|
+
widget.setProp("target-time", newTime);
|
|
68
|
+
}
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const handleBgColorChange = (value: string) => {
|
|
72
|
+
setBgColor(value);
|
|
73
|
+
widget.setProp("bg-color", value);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleTextColorChange = (value: string) => {
|
|
77
|
+
setTextColor(value);
|
|
78
|
+
widget.setProp("text-color", value);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleFontChange = (value: { font: string; textDecoration: string }) => {
|
|
82
|
+
setFont(value);
|
|
83
|
+
widget.setProp("font", JSON.stringify(value));
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<WixDesignSystemProvider>
|
|
88
|
+
<SidePanel width="300" height="100vh">
|
|
89
|
+
<SidePanel.Header title="Countdown Settings" />
|
|
90
|
+
<SidePanel.Content noPadding stretchVertically>
|
|
91
|
+
<Box direction="vertical" gap="24px">
|
|
92
|
+
<SidePanel.Field>
|
|
93
|
+
<FormField label="Title" required>
|
|
94
|
+
<Input
|
|
95
|
+
type="text"
|
|
96
|
+
value={title}
|
|
97
|
+
onChange={handleTitleChange}
|
|
98
|
+
placeholder="Enter countdown title"
|
|
99
|
+
/>
|
|
100
|
+
</FormField>
|
|
101
|
+
</SidePanel.Field>
|
|
102
|
+
|
|
103
|
+
<SidePanel.Field>
|
|
104
|
+
<FormField label="Target Date" required>
|
|
105
|
+
<Input
|
|
106
|
+
type="date"
|
|
107
|
+
value={targetDate}
|
|
108
|
+
onChange={handleDateChange}
|
|
109
|
+
/>
|
|
110
|
+
</FormField>
|
|
111
|
+
</SidePanel.Field>
|
|
112
|
+
|
|
113
|
+
<SidePanel.Field>
|
|
114
|
+
<FormField label="Target Time" required>
|
|
115
|
+
<TimeInput
|
|
116
|
+
value={parseTimeValue(targetTime)}
|
|
117
|
+
onChange={handleTimeChange}
|
|
118
|
+
/>
|
|
119
|
+
</FormField>
|
|
120
|
+
</SidePanel.Field>
|
|
121
|
+
|
|
122
|
+
<ColorPickerField
|
|
123
|
+
label="Background Color"
|
|
124
|
+
value={bgColor}
|
|
125
|
+
onChange={handleBgColorChange}
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
<ColorPickerField
|
|
129
|
+
label="Text Color"
|
|
130
|
+
value={textColor}
|
|
131
|
+
onChange={handleTextColorChange}
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
<FontPickerField
|
|
135
|
+
label="Text Font"
|
|
136
|
+
value={font}
|
|
137
|
+
onChange={handleFontChange}
|
|
138
|
+
/>
|
|
139
|
+
</Box>
|
|
140
|
+
</SidePanel.Content>
|
|
141
|
+
</SidePanel>
|
|
142
|
+
</WixDesignSystemProvider>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export default Panel;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
interface StyleProps {
|
|
4
|
+
bgColor: string;
|
|
5
|
+
textColor: string;
|
|
6
|
+
textFont?: string;
|
|
7
|
+
textDecoration?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface WidgetStyles {
|
|
11
|
+
wrapper: React.CSSProperties;
|
|
12
|
+
title: React.CSSProperties;
|
|
13
|
+
digits: React.CSSProperties;
|
|
14
|
+
block: React.CSSProperties;
|
|
15
|
+
number: React.CSSProperties;
|
|
16
|
+
label: React.CSSProperties;
|
|
17
|
+
separator: React.CSSProperties;
|
|
18
|
+
message: React.CSSProperties;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createStyles({ bgColor, textColor, textFont, textDecoration }: StyleProps): WidgetStyles {
|
|
22
|
+
return {
|
|
23
|
+
wrapper: {
|
|
24
|
+
backgroundColor: bgColor,
|
|
25
|
+
padding: '24px 32px',
|
|
26
|
+
textAlign: 'center',
|
|
27
|
+
display: 'inline-block',
|
|
28
|
+
},
|
|
29
|
+
title: {
|
|
30
|
+
font: textFont || '600 24px sans-serif',
|
|
31
|
+
color: textColor,
|
|
32
|
+
textDecoration,
|
|
33
|
+
marginBottom: '16px',
|
|
34
|
+
},
|
|
35
|
+
digits: {
|
|
36
|
+
display: 'flex',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
justifyContent: 'center',
|
|
39
|
+
gap: '16px',
|
|
40
|
+
},
|
|
41
|
+
block: {
|
|
42
|
+
display: 'flex',
|
|
43
|
+
flexDirection: 'column',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
padding: '12px 16px',
|
|
46
|
+
},
|
|
47
|
+
number: {
|
|
48
|
+
font: textFont || '600 40px sans-serif',
|
|
49
|
+
color: textColor,
|
|
50
|
+
textDecoration,
|
|
51
|
+
lineHeight: 1,
|
|
52
|
+
},
|
|
53
|
+
label: {
|
|
54
|
+
fontSize: '12px',
|
|
55
|
+
marginTop: '8px',
|
|
56
|
+
color: textColor,
|
|
57
|
+
opacity: 0.7,
|
|
58
|
+
},
|
|
59
|
+
separator: {
|
|
60
|
+
font: textFont || '600 32px sans-serif',
|
|
61
|
+
color: textColor,
|
|
62
|
+
textDecoration,
|
|
63
|
+
lineHeight: 1,
|
|
64
|
+
opacity: 0.5,
|
|
65
|
+
marginBottom: '20px',
|
|
66
|
+
},
|
|
67
|
+
message: {
|
|
68
|
+
fontSize: '18px',
|
|
69
|
+
color: textColor,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface TimeRemaining {
|
|
2
|
+
days: number;
|
|
3
|
+
hours: number;
|
|
4
|
+
minutes: number;
|
|
5
|
+
seconds: number;
|
|
6
|
+
isExpired: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const INITIAL_TIME: TimeRemaining = {
|
|
10
|
+
days: 0,
|
|
11
|
+
hours: 0,
|
|
12
|
+
minutes: 0,
|
|
13
|
+
seconds: 0,
|
|
14
|
+
isExpired: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function calculateTimeRemaining(targetDate: string, targetTime: string): TimeRemaining {
|
|
18
|
+
const target = new Date(`${targetDate}T${targetTime}`);
|
|
19
|
+
const diff = target.getTime() - Date.now();
|
|
20
|
+
|
|
21
|
+
if (diff <= 0) {
|
|
22
|
+
return { ...INITIAL_TIME, isExpired: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
26
|
+
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
27
|
+
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
28
|
+
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
|
29
|
+
|
|
30
|
+
return { days, hours, minutes, seconds, isExpired: false };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function padNumber(value: number): string {
|
|
34
|
+
return String(value).padStart(2, '0');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseTimeValue(timeString: string): Date {
|
|
38
|
+
const [hours, minutes] = timeString.split(':').map(Number);
|
|
39
|
+
const date = new Date();
|
|
40
|
+
date.setHours(hours || 0);
|
|
41
|
+
date.setMinutes(minutes || 0);
|
|
42
|
+
date.setSeconds(0);
|
|
43
|
+
date.setMilliseconds(0);
|
|
44
|
+
return date;
|
|
45
|
+
}
|
|
46
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React, { type FC, useState, useEffect } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import reactToWebComponent from 'react-to-webcomponent';
|
|
4
|
+
import { TimeBlock } from './components/TimeBlock';
|
|
5
|
+
import { Separator } from './components/Separator';
|
|
6
|
+
import { calculateTimeRemaining, padNumber, INITIAL_TIME, type TimeRemaining } from './utils';
|
|
7
|
+
import { createStyles } from './styles';
|
|
8
|
+
|
|
9
|
+
interface WidgetProps {
|
|
10
|
+
title?: string;
|
|
11
|
+
targetDate?: string;
|
|
12
|
+
targetTime?: string;
|
|
13
|
+
bgColor?: string;
|
|
14
|
+
textColor?: string;
|
|
15
|
+
font?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const CustomElement: FC<WidgetProps> = ({
|
|
19
|
+
title = 'Countdown',
|
|
20
|
+
targetDate = '',
|
|
21
|
+
targetTime = '00:00',
|
|
22
|
+
bgColor = '#ffffff',
|
|
23
|
+
textColor = '#333333',
|
|
24
|
+
font = "{}",
|
|
25
|
+
}) => {
|
|
26
|
+
const { font: textFont, textDecoration } = JSON.parse(font);
|
|
27
|
+
const [time, setTime] = useState<TimeRemaining>(INITIAL_TIME);
|
|
28
|
+
const styles = createStyles({ bgColor, textColor, textFont, textDecoration });
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!targetDate) return;
|
|
32
|
+
|
|
33
|
+
const update = () => setTime(calculateTimeRemaining(targetDate, targetTime));
|
|
34
|
+
update();
|
|
35
|
+
|
|
36
|
+
const interval = setInterval(update, 1000);
|
|
37
|
+
return () => clearInterval(interval);
|
|
38
|
+
}, [targetDate, targetTime]);
|
|
39
|
+
|
|
40
|
+
if (!targetDate) {
|
|
41
|
+
return (
|
|
42
|
+
<div style={styles.wrapper}>
|
|
43
|
+
{title && <div style={styles.title}>{title}</div>}
|
|
44
|
+
<div style={styles.message}>Set a target date</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (time.isExpired) {
|
|
50
|
+
return (
|
|
51
|
+
<div style={styles.wrapper}>
|
|
52
|
+
{title && <div style={styles.title}>{title}</div>}
|
|
53
|
+
<div style={styles.message}>Event has started!</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const timeUnits = [
|
|
59
|
+
{ value: time.days, label: 'Days' },
|
|
60
|
+
{ value: time.hours, label: 'Hours' },
|
|
61
|
+
{ value: time.minutes, label: 'Minutes' },
|
|
62
|
+
{ value: time.seconds, label: 'Seconds' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div style={styles.wrapper}>
|
|
67
|
+
{title && <div style={styles.title}>{title}</div>}
|
|
68
|
+
<div style={styles.digits}>
|
|
69
|
+
{timeUnits.map((unit, index) => (
|
|
70
|
+
<React.Fragment key={unit.label}>
|
|
71
|
+
{index > 0 && <Separator style={styles.separator} />}
|
|
72
|
+
<TimeBlock
|
|
73
|
+
value={padNumber(unit.value)}
|
|
74
|
+
label={unit.label}
|
|
75
|
+
numberStyle={styles.number}
|
|
76
|
+
labelStyle={styles.label}
|
|
77
|
+
blockStyle={styles.block}
|
|
78
|
+
/>
|
|
79
|
+
</React.Fragment>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
|
|
87
|
+
props: {
|
|
88
|
+
title: 'string',
|
|
89
|
+
targetDate: 'string',
|
|
90
|
+
targetTime: 'string',
|
|
91
|
+
bgColor: 'string',
|
|
92
|
+
textColor: 'string',
|
|
93
|
+
font: 'string',
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export default customElement;
|