@ledvance/ui-biz-bundle 1.1.70 → 1.1.71

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.
@@ -1,290 +1,290 @@
1
- import React, { useCallback, useEffect, useMemo } from 'react';
2
- import { ScrollView, StyleSheet, View } from 'react-native';
3
- import { cloneDeep, isEqual } from 'lodash';
4
- import { useReactive } from 'ahooks';
5
- import Page from '@ledvance/base/src/components/Page';
6
- import I18n from '@ledvance/base/src/i18n';
7
- import { useNavigation } from '@react-navigation/native';
8
- import TextField from '@ledvance/base/src/components/TextField';
9
- import { Utils } from 'tuya-panel-kit';
10
- import Card from '@ledvance/base/src/components/Card';
11
- import Spacer from '@ledvance/base/src/components/Spacer';
12
- import res from '@ledvance/base/src/res';
13
- import TextButton from '@ledvance/base/src/components/TextButton';
14
- import { useFanMaxSpeed } from '@ledvance/base/src/models/modules/NativePropsSlice';
15
- import FanAdjustView from '@ledvance/base/src/components/FanAdjustView';
16
- import { MoodNodeInfo, MoodPageParams, MoodUIInfo } from './Interface';
17
- import { Result } from '@ledvance/base/src/models/modules/Result';
18
- import { hsv2Hex, mapFloatToRange } from '@ledvance/base/src/utils';
19
- import { cctToColor } from '@ledvance/base/src/utils/cctUtils';
20
- import { useParams } from '@ledvance/base/src/hooks/Hooks';
21
- import LampAdjustView2 from '@ledvance/base/src/components/LampAdjustView2';
22
- import { ui_biz_routerKey } from '../../navigation/Routers'
23
- import LdvSwitch from '@ledvance/base/src/components/ldvSwitch';
24
- import { showDialog } from '@ledvance/base/src/utils/common';
25
-
26
- const cx = Utils.RatioUtils.convertX;
27
-
28
- export interface StaticMoodEditorPageParams {
29
- mode: 'add' | 'edit';
30
- currentMood: MoodUIInfo;
31
- moduleParams: MoodPageParams;
32
- nameRepeat: (mood: MoodUIInfo) => boolean
33
- modDeleteMood: (mode: 'add' | 'edit' | 'del', currentMood: MoodUIInfo) => Promise<Result<any>>;
34
- }
35
-
36
- export interface StaticMoodEditorPageState {
37
- headline: string;
38
- mood: MoodUIInfo;
39
- mainNode: MoodNodeInfo;
40
- secondaryNode: MoodNodeInfo;
41
- loading: boolean;
42
- }
43
-
44
- const StaticMoodEditorPage = () => {
45
- const navigation = useNavigation();
46
- const routeParams = useParams<StaticMoodEditorPageParams>();
47
- const params = cloneDeep(routeParams);
48
- const moduleParams = params.moduleParams;
49
- const isMix = !!(moduleParams.isCeilingLight || moduleParams.isMixLight);
50
- const state = useReactive<StaticMoodEditorPageState>({
51
- headline: '',
52
- mood: params.currentMood,
53
- mainNode: params.currentMood.mainLamp.nodes[0],
54
- secondaryNode: params.currentMood?.secondaryLamp?.nodes?.[0],
55
- loading: false,
56
- });
57
-
58
- useEffect(() => {
59
- state.headline = I18n.getLang(
60
- params.mode === 'add' ? 'add_new_static_mood_headline_text' : 'edit_static_mood_headline_text'
61
- );
62
- }, [params.mode]);
63
-
64
- const getColorBlockColor = useCallback((node: MoodNodeInfo) => {
65
- const s = Math.round(mapFloatToRange(node.s / 100, 30, 100));
66
- if (node.isColorNode) {
67
- return hsv2Hex(node.h, s, 100);
68
- } else {
69
- return cctToColor(node.colorTemp.toFixed());
70
- }
71
- }, []);
72
-
73
- const onRightClick = async () => {
74
- if (state.loading || !canSaveMoodData) return;
75
- state.loading = true;
76
- const newMood: MoodUIInfo = {
77
- ...state.mood,
78
- mainLamp: {
79
- ...state.mood.mainLamp,
80
- nodes: [cloneDeep(state.mainNode)],
81
- },
82
- secondaryLamp: {
83
- ...state.mood.secondaryLamp,
84
- id: undefined
85
- }
86
- };
87
- if (isMix && moduleParams.isMixLight) {
88
- newMood.secondaryLamp = {
89
- ...newMood.secondaryLamp,
90
- nodes: [cloneDeep(state.secondaryNode)],
91
- };
92
- newMood.mainLamp.type = 2
93
- if(moduleParams.isSupportBrightness && !moduleParams.isSupportTemperature){
94
- newMood.mainLamp.type = 1
95
- }
96
- if(moduleParams.isSupportColor){
97
- newMood.secondaryLamp.type = 3
98
- }
99
- }
100
- const res = await params.modDeleteMood(params.mode, newMood);
101
- state.loading = false;
102
- if (res.success) {
103
- navigation.navigate(ui_biz_routerKey.ui_biz_mood);
104
- }
105
- };
106
-
107
- const nameRepeat = useMemo(() => {
108
- return params.nameRepeat(state.mood)
109
- }, [state.mood.name]);
110
-
111
- const checkMoodChanged = useMemo(() =>{
112
- return isEqual(state.mood, params.currentMood)
113
- }, [JSON.stringify(state.mood), params.currentMood])
114
-
115
- const canSaveMoodData = useMemo(() =>{
116
- return state.mood.name.length > 0 && state.mood.name.length < 33 && !nameRepeat && (params.mode === 'add' || !checkMoodChanged)
117
- }, [nameRepeat, state.mood.name, checkMoodChanged, params.mode])
118
-
119
- return (
120
- <Page
121
- backText={I18n.getLang('mesh_device_detail_mode')}
122
- showBackDialog={!checkMoodChanged}
123
- backDialogTitle={I18n.getLang(
124
- params.mode === 'add'
125
- ? 'string_light_pp_dialog_sm_add_headline_c'
126
- : 'manage_user_unsaved_changes_dialog_headline'
127
- )}
128
- backDialogContent={I18n.getLang(
129
- params.mode === 'add'
130
- ? 'strip_light_static_mood_add_step_2_dialog_text'
131
- : 'strip_light_static_mood_editor_step_2_dialog_text'
132
- )}
133
- headlineText={state.headline}
134
- rightButtonIcon={canSaveMoodData ? res.ic_check : res.ic_uncheck}
135
- rightButtonIconClick={onRightClick}
136
- loading={state.loading}
137
- >
138
- <ScrollView style={{ flex: 1 }} nestedScrollEnabled={true}>
139
- <View style={styles.root}>
140
- <TextField
141
- style={styles.name}
142
- value={state.mood.name}
143
- placeholder={I18n.getLang('edit_static_mood_inputfield_topic_text')}
144
- onChangeText={text => {
145
- state.mood.name = text;
146
- }}
147
- maxLength={33}
148
- showError={state.mood.name.length > 32 || nameRepeat}
149
- tipColor={nameRepeat ? '#f00' : undefined}
150
- tipIcon={nameRepeat ? res.ic_text_field_input_error : undefined}
151
- errorText={I18n.getLang(
152
- nameRepeat ? 'string_light_pp_field_sm_add_error1' : 'add_new_dynamic_mood_alert_text'
153
- )}
154
- />
155
- <Card style={styles.adjustCard}>
156
- <LdvSwitch
157
- title={I18n.getLang(
158
- isMix
159
- ? 'light_sources_tile_main_lighting_headline'
160
- : 'light_sources_tile_tw_lighting_headline'
161
- )}
162
- color={getColorBlockColor(state.mainNode)}
163
- colorAlpha={1}
164
- enable={!!state.mood.mainLamp.enable}
165
- setEnable={v => {
166
- state.mood.mainLamp.enable = v;
167
- }}
168
- showSwitch={!!moduleParams.isMixLight}
169
- />
170
- {(!moduleParams.isMixLight || state.mood.mainLamp.enable) && (
171
- <LampAdjustView2
172
- isSupportColor={isMix ? false : moduleParams.isSupportColor}
173
- isSupportBrightness={moduleParams.isSupportBrightness}
174
- isSupportCCT={moduleParams.isSupportTemperature}
175
- isColorMode={state.mainNode.isColorNode}
176
- reserveSV={true}
177
- setIsColorMode={isColorMode => {
178
- state.mainNode.isColorNode = isColorMode;
179
- }}
180
- hsv={state.mainNode}
181
- onHSVChange={(hsv) => {
182
- state.mainNode.h = hsv.h
183
- state.mainNode.s = hsv.s
184
- state.mainNode.v = hsv.v
185
- }}
186
- cct={state.mainNode.colorTemp}
187
- brightness={state.mainNode.brightness}
188
- onCCTChange={(cct) => {
189
- state.mainNode.colorTemp = cct;
190
- }}
191
- onBrightnessChange={(brightness) => {
192
- state.mainNode.brightness = brightness;
193
- }}
194
- />
195
- )}
196
- </Card>
197
- <Spacer />
198
- {!!(moduleParams.isFanLight || moduleParams.isUVCFan) && (
199
- <FanAdjustView
200
- fanEnable={!!state.mood.mainLamp.fanEnable}
201
- fanSpeed={state.mood.mainLamp.fanSpeed || 1}
202
- maxFanSpeed={useFanMaxSpeed()}
203
- onFanSwitch={fanEnable => {
204
- state.mood.mainLamp.fanEnable = fanEnable;
205
- }}
206
- onFanSpeedChange={fanSpeed => {
207
- state.mood.mainLamp.fanSpeed = fanSpeed;
208
- }}
209
- onFanSpeedChangeComplete={fanSpeed => {
210
- state.mood.mainLamp.fanSpeed = fanSpeed;
211
- }}
212
- style={styles.fanAdjustCard}
213
- />
214
- )}
215
- {params.mode === 'edit' && (
216
- <View style={{ marginTop: cx(20), marginHorizontal: cx(24) }}>
217
- <TextButton
218
- style={styles.deleteBtn}
219
- textStyle={styles.deleteBtnText}
220
- text={I18n.getLang('edit_static_mood_button_delete_text')}
221
- onPress={() => {
222
- showDialog({
223
- method: 'confirm',
224
- title: I18n.getLang('string_light_pp_dialog_sm_ed_headline_d'),
225
- subTitle: I18n.getLang(`strip_light_static_mood_edit_dialog_text`),
226
- onConfirm: async (_, {close})=>{
227
- close();
228
- state.loading = true;
229
- const res = await params.modDeleteMood('del', state.mood);
230
- state.loading = false;
231
- if (res.success) {
232
- navigation.navigate(ui_biz_routerKey.ui_biz_mood);
233
- }
234
- }
235
- })
236
- }}
237
- />
238
- </View>
239
- )}
240
- <Spacer />
241
- </View>
242
- </ScrollView>
243
- </Page>
244
- );
245
- };
246
-
247
- const styles = StyleSheet.create({
248
- root: {
249
- flex: 1,
250
- flexDirection: 'column',
251
- },
252
- name: {
253
- marginHorizontal: cx(24),
254
- },
255
- adjustCard: {
256
- marginTop: cx(12),
257
- marginHorizontal: cx(24),
258
- },
259
- fanAdjustCard: {
260
- marginHorizontal: cx(24),
261
- },
262
- lightLine: {
263
- flexDirection: 'row',
264
- marginHorizontal: cx(16),
265
- },
266
- light: {
267
- color: '#000',
268
- fontSize: cx(18),
269
- fontFamily: 'helvetica_neue_lt_std_bd',
270
- },
271
- preview: {
272
- width: cx(20),
273
- height: cx(20),
274
- marginStart: cx(12),
275
- borderRadius: cx(4),
276
- },
277
- deleteBtn: {
278
- width: '100%',
279
- height: cx(50),
280
- backgroundColor: '#666',
281
- borderRadius: cx(8),
282
- },
283
- deleteBtnText: {
284
- color: '#fff',
285
- fontSize: cx(16),
286
- fontFamily: 'helvetica_neue_lt_std_bd',
287
- },
288
- });
289
-
290
- export default StaticMoodEditorPage;
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
2
+ import { ScrollView, StyleSheet, View } from 'react-native';
3
+ import { cloneDeep, isEqual } from 'lodash';
4
+ import { useReactive } from 'ahooks';
5
+ import Page from '@ledvance/base/src/components/Page';
6
+ import I18n from '@ledvance/base/src/i18n';
7
+ import { useNavigation } from '@react-navigation/native';
8
+ import TextField from '@ledvance/base/src/components/TextField';
9
+ import { Utils } from 'tuya-panel-kit';
10
+ import Card from '@ledvance/base/src/components/Card';
11
+ import Spacer from '@ledvance/base/src/components/Spacer';
12
+ import res from '@ledvance/base/src/res';
13
+ import TextButton from '@ledvance/base/src/components/TextButton';
14
+ import { useFanMaxSpeed } from '@ledvance/base/src/models/modules/NativePropsSlice';
15
+ import FanAdjustView from '@ledvance/base/src/components/FanAdjustView';
16
+ import { MoodNodeInfo, MoodPageParams, MoodUIInfo } from './Interface';
17
+ import { Result } from '@ledvance/base/src/models/modules/Result';
18
+ import { hsv2Hex, mapFloatToRange } from '@ledvance/base/src/utils';
19
+ import { cctToColor } from '@ledvance/base/src/utils/cctUtils';
20
+ import { useParams } from '@ledvance/base/src/hooks/Hooks';
21
+ import LampAdjustView2 from '@ledvance/base/src/components/LampAdjustView2';
22
+ import { ui_biz_routerKey } from '../../navigation/Routers'
23
+ import LdvSwitch from '@ledvance/base/src/components/ldvSwitch';
24
+ import { showDialog } from '@ledvance/base/src/utils/common';
25
+
26
+ const cx = Utils.RatioUtils.convertX;
27
+
28
+ export interface StaticMoodEditorPageParams {
29
+ mode: 'add' | 'edit';
30
+ currentMood: MoodUIInfo;
31
+ moduleParams: MoodPageParams;
32
+ nameRepeat: (mood: MoodUIInfo) => boolean
33
+ modDeleteMood: (mode: 'add' | 'edit' | 'del', currentMood: MoodUIInfo) => Promise<Result<any>>;
34
+ }
35
+
36
+ export interface StaticMoodEditorPageState {
37
+ headline: string;
38
+ mood: MoodUIInfo;
39
+ mainNode: MoodNodeInfo;
40
+ secondaryNode: MoodNodeInfo;
41
+ loading: boolean;
42
+ }
43
+
44
+ const StaticMoodEditorPage = () => {
45
+ const navigation = useNavigation();
46
+ const routeParams = useParams<StaticMoodEditorPageParams>();
47
+ const params = cloneDeep(routeParams);
48
+ const moduleParams = params.moduleParams;
49
+ const isMix = !!(moduleParams.isCeilingLight || moduleParams.isMixLight);
50
+ const state = useReactive<StaticMoodEditorPageState>({
51
+ headline: '',
52
+ mood: params.currentMood,
53
+ mainNode: params.currentMood.mainLamp.nodes[0],
54
+ secondaryNode: params.currentMood?.secondaryLamp?.nodes?.[0],
55
+ loading: false,
56
+ });
57
+
58
+ useEffect(() => {
59
+ state.headline = I18n.getLang(
60
+ params.mode === 'add' ? 'add_new_static_mood_headline_text' : 'edit_static_mood_headline_text'
61
+ );
62
+ }, [params.mode]);
63
+
64
+ const getColorBlockColor = useCallback((node: MoodNodeInfo) => {
65
+ const s = Math.round(mapFloatToRange(node.s / 100, 30, 100));
66
+ if (node.isColorNode) {
67
+ return hsv2Hex(node.h, s, 100);
68
+ } else {
69
+ return cctToColor(node.colorTemp.toFixed());
70
+ }
71
+ }, []);
72
+
73
+ const onRightClick = async () => {
74
+ if (state.loading || !canSaveMoodData) return;
75
+ state.loading = true;
76
+ const newMood: MoodUIInfo = {
77
+ ...state.mood,
78
+ mainLamp: {
79
+ ...state.mood.mainLamp,
80
+ nodes: [cloneDeep(state.mainNode)],
81
+ },
82
+ secondaryLamp: {
83
+ ...state.mood.secondaryLamp,
84
+ id: undefined
85
+ }
86
+ };
87
+ if (isMix && moduleParams.isMixLight) {
88
+ newMood.secondaryLamp = {
89
+ ...newMood.secondaryLamp,
90
+ nodes: [cloneDeep(state.secondaryNode)],
91
+ };
92
+ newMood.mainLamp.type = 2
93
+ if(moduleParams.isSupportBrightness && !moduleParams.isSupportTemperature){
94
+ newMood.mainLamp.type = 1
95
+ }
96
+ if(moduleParams.isSupportColor){
97
+ newMood.secondaryLamp.type = 3
98
+ }
99
+ }
100
+ const res = await params.modDeleteMood(params.mode, newMood);
101
+ state.loading = false;
102
+ if (res.success) {
103
+ navigation.navigate(ui_biz_routerKey.ui_biz_mood);
104
+ }
105
+ };
106
+
107
+ const nameRepeat = useMemo(() => {
108
+ return params.nameRepeat(state.mood)
109
+ }, [state.mood.name]);
110
+
111
+ const checkMoodChanged = useMemo(() =>{
112
+ return isEqual(state.mood, params.currentMood)
113
+ }, [JSON.stringify(state.mood), params.currentMood])
114
+
115
+ const canSaveMoodData = useMemo(() =>{
116
+ return state.mood.name.length > 0 && state.mood.name.length < 33 && !nameRepeat && (params.mode === 'add' || !checkMoodChanged)
117
+ }, [nameRepeat, state.mood.name, checkMoodChanged, params.mode])
118
+
119
+ return (
120
+ <Page
121
+ backText={I18n.getLang('mesh_device_detail_mode')}
122
+ showBackDialog={!checkMoodChanged}
123
+ backDialogTitle={I18n.getLang(
124
+ params.mode === 'add'
125
+ ? 'string_light_pp_dialog_sm_add_headline_c'
126
+ : 'manage_user_unsaved_changes_dialog_headline'
127
+ )}
128
+ backDialogContent={I18n.getLang(
129
+ params.mode === 'add'
130
+ ? 'strip_light_static_mood_add_step_2_dialog_text'
131
+ : 'strip_light_static_mood_editor_step_2_dialog_text'
132
+ )}
133
+ headlineText={state.headline}
134
+ rightButtonIcon={canSaveMoodData ? res.ic_check : res.ic_uncheck}
135
+ rightButtonIconClick={onRightClick}
136
+ loading={state.loading}
137
+ >
138
+ <ScrollView style={{ flex: 1 }} nestedScrollEnabled={true}>
139
+ <View style={styles.root}>
140
+ <TextField
141
+ style={styles.name}
142
+ value={state.mood.name}
143
+ placeholder={I18n.getLang('edit_static_mood_inputfield_topic_text')}
144
+ onChangeText={text => {
145
+ state.mood.name = text;
146
+ }}
147
+ maxLength={33}
148
+ showError={state.mood.name.length > 32 || nameRepeat}
149
+ tipColor={nameRepeat ? '#f00' : undefined}
150
+ tipIcon={nameRepeat ? res.ic_text_field_input_error : undefined}
151
+ errorText={I18n.getLang(
152
+ nameRepeat ? 'string_light_pp_field_sm_add_error1' : 'add_new_dynamic_mood_alert_text'
153
+ )}
154
+ />
155
+ <Card style={styles.adjustCard}>
156
+ <LdvSwitch
157
+ title={I18n.getLang(
158
+ isMix
159
+ ? 'light_sources_tile_main_lighting_headline'
160
+ : 'light_sources_tile_tw_lighting_headline'
161
+ )}
162
+ color={getColorBlockColor(state.mainNode)}
163
+ colorAlpha={1}
164
+ enable={!!state.mood.mainLamp.enable}
165
+ setEnable={v => {
166
+ state.mood.mainLamp.enable = v;
167
+ }}
168
+ showSwitch={!!moduleParams.isMixLight}
169
+ />
170
+ {(!moduleParams.isMixLight || state.mood.mainLamp.enable) && (
171
+ <LampAdjustView2
172
+ isSupportColor={isMix ? false : moduleParams.isSupportColor}
173
+ isSupportBrightness={moduleParams.isSupportBrightness}
174
+ isSupportCCT={moduleParams.isSupportTemperature}
175
+ isColorMode={state.mainNode.isColorNode}
176
+ reserveSV={true}
177
+ setIsColorMode={isColorMode => {
178
+ state.mainNode.isColorNode = isColorMode;
179
+ }}
180
+ hsv={state.mainNode}
181
+ onHSVChange={(hsv) => {
182
+ state.mainNode.h = hsv.h
183
+ state.mainNode.s = hsv.s
184
+ state.mainNode.v = hsv.v
185
+ }}
186
+ cct={state.mainNode.colorTemp}
187
+ brightness={state.mainNode.brightness}
188
+ onCCTChange={(cct) => {
189
+ state.mainNode.colorTemp = cct;
190
+ }}
191
+ onBrightnessChange={(brightness) => {
192
+ state.mainNode.brightness = brightness;
193
+ }}
194
+ />
195
+ )}
196
+ </Card>
197
+ <Spacer />
198
+ {!!(moduleParams.isFanLight || moduleParams.isUVCFan) && (
199
+ <FanAdjustView
200
+ fanEnable={!!state.mood.mainLamp.fanEnable}
201
+ fanSpeed={state.mood.mainLamp.fanSpeed || 1}
202
+ maxFanSpeed={useFanMaxSpeed()}
203
+ onFanSwitch={fanEnable => {
204
+ state.mood.mainLamp.fanEnable = fanEnable;
205
+ }}
206
+ onFanSpeedChange={fanSpeed => {
207
+ state.mood.mainLamp.fanSpeed = fanSpeed;
208
+ }}
209
+ onFanSpeedChangeComplete={fanSpeed => {
210
+ state.mood.mainLamp.fanSpeed = fanSpeed;
211
+ }}
212
+ style={styles.fanAdjustCard}
213
+ />
214
+ )}
215
+ {params.mode === 'edit' && (
216
+ <View style={{ marginTop: cx(20), marginHorizontal: cx(24) }}>
217
+ <TextButton
218
+ style={styles.deleteBtn}
219
+ textStyle={styles.deleteBtnText}
220
+ text={I18n.getLang('edit_static_mood_button_delete_text')}
221
+ onPress={() => {
222
+ showDialog({
223
+ method: 'confirm',
224
+ title: I18n.getLang('string_light_pp_dialog_sm_ed_headline_d'),
225
+ subTitle: I18n.getLang(`strip_light_static_mood_edit_dialog_text`),
226
+ onConfirm: async (_, {close})=>{
227
+ close();
228
+ state.loading = true;
229
+ const res = await params.modDeleteMood('del', state.mood);
230
+ state.loading = false;
231
+ if (res.success) {
232
+ navigation.navigate(ui_biz_routerKey.ui_biz_mood);
233
+ }
234
+ }
235
+ })
236
+ }}
237
+ />
238
+ </View>
239
+ )}
240
+ <Spacer />
241
+ </View>
242
+ </ScrollView>
243
+ </Page>
244
+ );
245
+ };
246
+
247
+ const styles = StyleSheet.create({
248
+ root: {
249
+ flex: 1,
250
+ flexDirection: 'column',
251
+ },
252
+ name: {
253
+ marginHorizontal: cx(24),
254
+ },
255
+ adjustCard: {
256
+ marginTop: cx(12),
257
+ marginHorizontal: cx(24),
258
+ },
259
+ fanAdjustCard: {
260
+ marginHorizontal: cx(24),
261
+ },
262
+ lightLine: {
263
+ flexDirection: 'row',
264
+ marginHorizontal: cx(16),
265
+ },
266
+ light: {
267
+ color: '#000',
268
+ fontSize: cx(18),
269
+ fontFamily: 'helvetica_neue_lt_std_bd',
270
+ },
271
+ preview: {
272
+ width: cx(20),
273
+ height: cx(20),
274
+ marginStart: cx(12),
275
+ borderRadius: cx(4),
276
+ },
277
+ deleteBtn: {
278
+ width: '100%',
279
+ height: cx(50),
280
+ backgroundColor: '#666',
281
+ borderRadius: cx(8),
282
+ },
283
+ deleteBtnText: {
284
+ color: '#fff',
285
+ fontSize: cx(16),
286
+ fontFamily: 'helvetica_neue_lt_std_bd',
287
+ },
288
+ });
289
+
290
+ export default StaticMoodEditorPage;