@lobehub/chat 0.140.1 → 0.141.1
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/CHANGELOG.md +50 -0
- package/README.zh-CN.md +4 -1
- package/docs/self-hosting/advanced/analytics.mdx +1 -1
- package/docs/self-hosting/advanced/analytics.zh-CN.mdx +1 -1
- package/docs/self-hosting/environment-variables/analytics.mdx +91 -0
- package/docs/self-hosting/environment-variables/analytics.zh-CN.mdx +91 -0
- package/docs/self-hosting/environment-variables/basic.mdx +16 -89
- package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +17 -88
- package/locales/ar/common.json +34 -6
- package/locales/ar/setting.json +36 -0
- package/locales/de-DE/common.json +34 -6
- package/locales/de-DE/setting.json +36 -0
- package/locales/en-US/common.json +34 -6
- package/locales/en-US/setting.json +36 -0
- package/locales/es-ES/common.json +34 -6
- package/locales/es-ES/setting.json +36 -0
- package/locales/fr-FR/common.json +34 -6
- package/locales/fr-FR/setting.json +36 -0
- package/locales/it-IT/common.json +34 -6
- package/locales/it-IT/setting.json +38 -0
- package/locales/ja-JP/common.json +34 -6
- package/locales/ja-JP/setting.json +38 -0
- package/locales/ko-KR/common.json +34 -6
- package/locales/ko-KR/setting.json +36 -0
- package/locales/nl-NL/common.json +34 -6
- package/locales/nl-NL/setting.json +38 -0
- package/locales/pl-PL/common.json +34 -6
- package/locales/pl-PL/setting.json +36 -0
- package/locales/pt-BR/common.json +34 -6
- package/locales/pt-BR/setting.json +36 -0
- package/locales/ru-RU/common.json +34 -6
- package/locales/ru-RU/setting.json +36 -0
- package/locales/tr-TR/common.json +34 -6
- package/locales/tr-TR/setting.json +36 -0
- package/locales/vi-VN/common.json +34 -6
- package/locales/vi-VN/setting.json +36 -0
- package/locales/zh-CN/common.json +34 -6
- package/locales/zh-CN/setting.json +36 -0
- package/locales/zh-TW/common.json +34 -6
- package/locales/zh-TW/setting.json +36 -0
- package/package.json +12 -5
- package/src/app/chat/(desktop)/features/SessionHeader.tsx +5 -1
- package/src/app/chat/(mobile)/features/SessionHeader.tsx +9 -4
- package/src/app/chat/features/SessionListContent/List/SkeletonList.tsx +0 -1
- package/src/app/layout.tsx +2 -0
- package/src/app/settings/(desktop)/features/Header.tsx +11 -1
- package/src/app/settings/(mobile)/features/Header/index.tsx +12 -1
- package/src/app/settings/features/SettingList/index.tsx +2 -1
- package/src/app/settings/sync/Alert.tsx +39 -0
- package/src/app/settings/sync/DeviceInfo/Card.tsx +41 -0
- package/src/app/settings/sync/DeviceInfo/DeviceName.tsx +66 -0
- package/src/app/settings/sync/DeviceInfo/index.tsx +117 -0
- package/src/app/settings/sync/PageTitle.tsx +11 -0
- package/src/app/settings/sync/WebRTC/ChannelNameInput.tsx +46 -0
- package/src/app/settings/sync/WebRTC/index.tsx +97 -0
- package/src/app/settings/sync/components/SyncSwitch/index.css +237 -0
- package/src/app/settings/sync/components/SyncSwitch/index.tsx +79 -0
- package/src/app/settings/sync/components/SystemIcon.tsx +16 -0
- package/src/app/settings/sync/layout.tsx +9 -0
- package/src/app/settings/sync/page.tsx +23 -0
- package/src/app/settings/sync/util.ts +4 -0
- package/src/components/Analytics/Google.tsx +14 -0
- package/src/components/Analytics/Vercel.tsx +2 -4
- package/src/components/Analytics/index.tsx +9 -4
- package/src/components/BrowserIcon/components/Brave.tsx +56 -0
- package/src/components/BrowserIcon/components/Chrome.tsx +14 -0
- package/src/components/BrowserIcon/components/Chromium.tsx +14 -0
- package/src/components/BrowserIcon/components/Edge.tsx +36 -0
- package/src/components/BrowserIcon/components/Firefox.tsx +38 -0
- package/src/components/BrowserIcon/components/Opera.tsx +19 -0
- package/src/components/BrowserIcon/components/Safari.tsx +23 -0
- package/src/components/BrowserIcon/components/Samsung.tsx +21 -0
- package/src/components/BrowserIcon/index.tsx +50 -0
- package/src/components/BrowserIcon/types.ts +8 -0
- package/src/config/__tests__/client.test.ts +1 -8
- package/src/config/client.ts +0 -7
- package/src/config/server/analytics.ts +32 -0
- package/src/config/server/index.ts +3 -1
- package/src/const/settings.ts +6 -0
- package/src/database/core/__tests__/model.test.ts +2 -2
- package/src/database/core/db.ts +1 -1
- package/src/database/core/index.ts +1 -0
- package/src/database/core/model.ts +83 -5
- package/src/database/core/sync.ts +328 -0
- package/src/database/models/__tests__/message.test.ts +0 -1
- package/src/database/models/__tests__/plugin.test.ts +5 -2
- package/src/database/models/file.ts +1 -1
- package/src/database/models/message.ts +49 -30
- package/src/database/models/plugin.ts +6 -5
- package/src/database/models/session.ts +15 -16
- package/src/database/models/sessionGroup.ts +14 -8
- package/src/database/models/topic.ts +14 -21
- package/src/features/SyncStatusInspector/DisableSync.tsx +79 -0
- package/src/features/SyncStatusInspector/EnableSync.tsx +136 -0
- package/src/features/SyncStatusInspector/EnableTag.tsx +66 -0
- package/src/features/SyncStatusInspector/index.tsx +27 -0
- package/src/hooks/useSyncData.ts +48 -0
- package/src/layout/GlobalLayout/StoreHydration.tsx +5 -0
- package/src/locales/default/common.ts +27 -5
- package/src/locales/default/setting.ts +37 -1
- package/src/services/chat.ts +6 -2
- package/src/services/config.ts +1 -1
- package/src/services/global.ts +15 -0
- package/src/store/chat/slices/topic/action.test.ts +1 -1
- package/src/store/chat/slices/topic/action.ts +21 -10
- package/src/store/global/slices/common/action.ts +71 -1
- package/src/store/global/slices/common/initialState.ts +9 -0
- package/src/store/global/slices/common/selectors.ts +1 -0
- package/src/store/global/slices/preference/initialState.ts +2 -1
- package/src/store/global/slices/preference/selectors.ts +3 -0
- package/src/store/global/slices/settings/selectors/index.ts +1 -0
- package/src/store/global/slices/settings/selectors/sync.ts +14 -0
- package/src/types/settings/index.ts +3 -0
- package/src/types/settings/sync.ts +10 -0
- package/src/types/sync.ts +41 -0
- package/src/utils/platform.ts +9 -3
- package/src/utils/responsive.ts +21 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createStyles } from 'antd-style';
|
|
2
|
+
import { ReactNode, memo } from 'react';
|
|
3
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
|
4
|
+
|
|
5
|
+
const useStyles = createStyles(({ css, token, responsive }) => ({
|
|
6
|
+
container: css`
|
|
7
|
+
background: ${token.colorFillTertiary};
|
|
8
|
+
border-radius: 12px;
|
|
9
|
+
|
|
10
|
+
.${responsive.mobile} {
|
|
11
|
+
width: 100%;
|
|
12
|
+
}
|
|
13
|
+
`,
|
|
14
|
+
icon: css`
|
|
15
|
+
width: 40px;
|
|
16
|
+
height: 40px;
|
|
17
|
+
`,
|
|
18
|
+
title: css`
|
|
19
|
+
font-size: 20px;
|
|
20
|
+
`,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const Card = memo<{ icon: ReactNode; title: string }>(({ title, icon }) => {
|
|
24
|
+
const { styles } = useStyles();
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Flexbox
|
|
28
|
+
align={'center'}
|
|
29
|
+
className={styles.container}
|
|
30
|
+
gap={12}
|
|
31
|
+
horizontal
|
|
32
|
+
paddingBlock={12}
|
|
33
|
+
paddingInline={20}
|
|
34
|
+
>
|
|
35
|
+
<Center className={styles.icon}>{icon}</Center>
|
|
36
|
+
<Flexbox className={styles.title}>{title}</Flexbox>
|
|
37
|
+
</Flexbox>
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export default Card;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { EditableText } from '@lobehub/ui';
|
|
4
|
+
import { Typography } from 'antd';
|
|
5
|
+
import { memo, useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
|
8
|
+
|
|
9
|
+
import { useGlobalStore } from '@/store/global';
|
|
10
|
+
import { syncSettingsSelectors } from '@/store/global/selectors';
|
|
11
|
+
|
|
12
|
+
const DeviceName = memo(() => {
|
|
13
|
+
const { t } = useTranslation('setting');
|
|
14
|
+
|
|
15
|
+
const [deviceName, setSettings] = useGlobalStore((s) => [
|
|
16
|
+
syncSettingsSelectors.deviceName(s),
|
|
17
|
+
s.setSettings,
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const [editing, setEditing] = useState(false);
|
|
21
|
+
|
|
22
|
+
const updateDeviceName = (deviceName: string) => {
|
|
23
|
+
setSettings({ sync: { deviceName } });
|
|
24
|
+
setEditing(false);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Flexbox gap={4}>
|
|
29
|
+
<Flexbox
|
|
30
|
+
align={'center'}
|
|
31
|
+
height={40}
|
|
32
|
+
horizontal
|
|
33
|
+
style={{ fontSize: 20, fontWeight: 'bold' }}
|
|
34
|
+
width={'100%'}
|
|
35
|
+
>
|
|
36
|
+
{!deviceName && !editing && (
|
|
37
|
+
<Flexbox
|
|
38
|
+
onClick={() => {
|
|
39
|
+
setEditing(true);
|
|
40
|
+
}}
|
|
41
|
+
style={{ cursor: 'pointer' }}
|
|
42
|
+
>
|
|
43
|
+
<Typography.Text type={'secondary'}>{t('sync.device.deviceName.hint')}</Typography.Text>
|
|
44
|
+
</Flexbox>
|
|
45
|
+
)}
|
|
46
|
+
<EditableText
|
|
47
|
+
editing={editing}
|
|
48
|
+
onBlur={(e) => {
|
|
49
|
+
updateDeviceName(e.target.value);
|
|
50
|
+
}}
|
|
51
|
+
onChange={(e) => {
|
|
52
|
+
updateDeviceName(e);
|
|
53
|
+
}}
|
|
54
|
+
onEditingChange={setEditing}
|
|
55
|
+
placeholder={t('sync.device.deviceName.placeholder')}
|
|
56
|
+
size={'large'}
|
|
57
|
+
style={{ maxWidth: 200 }}
|
|
58
|
+
type={'block'}
|
|
59
|
+
value={deviceName}
|
|
60
|
+
/>
|
|
61
|
+
</Flexbox>
|
|
62
|
+
</Flexbox>
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export default DeviceName;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Typography } from 'antd';
|
|
4
|
+
import { createStyles } from 'antd-style';
|
|
5
|
+
import { rgba } from 'polished';
|
|
6
|
+
import { memo } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { Flexbox } from 'react-layout-kit';
|
|
9
|
+
|
|
10
|
+
import { BrowserIcon } from '@/components/BrowserIcon';
|
|
11
|
+
import { MAX_WIDTH } from '@/const/layoutTokens';
|
|
12
|
+
|
|
13
|
+
import SystemIcon from '../components/SystemIcon';
|
|
14
|
+
import Card from './Card';
|
|
15
|
+
import DeviceName from './DeviceName';
|
|
16
|
+
|
|
17
|
+
const useStyles = createStyles(({ css, cx, responsive, isDarkMode, token, stylish }) => ({
|
|
18
|
+
cards: css`
|
|
19
|
+
flex-direction: row;
|
|
20
|
+
${responsive.mobile} {
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
`,
|
|
25
|
+
container: css`
|
|
26
|
+
position: relative;
|
|
27
|
+
width: 100%;
|
|
28
|
+
border-radius: ${token.borderRadiusLG}px;
|
|
29
|
+
`,
|
|
30
|
+
content: cx(
|
|
31
|
+
stylish.blurStrong,
|
|
32
|
+
css`
|
|
33
|
+
z-index: 2;
|
|
34
|
+
|
|
35
|
+
flex-direction: row;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
|
|
38
|
+
height: 88px;
|
|
39
|
+
padding: 12px;
|
|
40
|
+
|
|
41
|
+
background: ${rgba(token.colorBgContainer, isDarkMode ? 0.7 : 1)};
|
|
42
|
+
border-radius: ${token.borderRadiusLG - 1}px;
|
|
43
|
+
|
|
44
|
+
${responsive.mobile} {
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 16px;
|
|
47
|
+
align-items: flex-start;
|
|
48
|
+
|
|
49
|
+
width: 100%;
|
|
50
|
+
padding: 8px;
|
|
51
|
+
}
|
|
52
|
+
`,
|
|
53
|
+
),
|
|
54
|
+
glow: cx(
|
|
55
|
+
stylish.gradientAnimation,
|
|
56
|
+
css`
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
opacity: 0.5;
|
|
59
|
+
background-image: linear-gradient(
|
|
60
|
+
-45deg,
|
|
61
|
+
${isDarkMode ? token.geekblue4 : token.geekblue},
|
|
62
|
+
${isDarkMode ? token.cyan4 : token.cyan}
|
|
63
|
+
);
|
|
64
|
+
animation-duration: 10s;
|
|
65
|
+
`,
|
|
66
|
+
),
|
|
67
|
+
wrapper: css`
|
|
68
|
+
${responsive.mobile} {
|
|
69
|
+
padding-block: 8px;
|
|
70
|
+
padding-inline: 4px;
|
|
71
|
+
}
|
|
72
|
+
`,
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
interface DeviceCardProps {
|
|
76
|
+
browser?: string;
|
|
77
|
+
os?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const DeviceCard = memo<DeviceCardProps>(({ browser, os }) => {
|
|
81
|
+
const { styles } = useStyles();
|
|
82
|
+
const { t } = useTranslation('setting');
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Flexbox
|
|
86
|
+
className={styles.wrapper}
|
|
87
|
+
style={{ maxWidth: MAX_WIDTH, position: 'relative' }}
|
|
88
|
+
width={'100%'}
|
|
89
|
+
>
|
|
90
|
+
<Flexbox className={styles.container} padding={4}>
|
|
91
|
+
<Flexbox horizontal paddingBlock={8} paddingInline={12}>
|
|
92
|
+
<div>
|
|
93
|
+
<Typography style={{ fontWeight: 'bold' }}>{t('sync.device.title')}</Typography>
|
|
94
|
+
</div>
|
|
95
|
+
</Flexbox>
|
|
96
|
+
<Flexbox align={'center'} className={styles.content} flex={1} padding={12}>
|
|
97
|
+
<DeviceName />
|
|
98
|
+
<Flexbox className={styles.cards} gap={12}>
|
|
99
|
+
<Card icon={<SystemIcon title={os} />} title={os || t('sync.device.unknownOS')} />
|
|
100
|
+
<Card
|
|
101
|
+
icon={browser && <BrowserIcon browser={browser} size={32} />}
|
|
102
|
+
title={browser || t('sync.device.unknownBrowser')}
|
|
103
|
+
/>
|
|
104
|
+
</Flexbox>
|
|
105
|
+
</Flexbox>
|
|
106
|
+
<Flexbox
|
|
107
|
+
className={styles.glow}
|
|
108
|
+
height={'100%'}
|
|
109
|
+
style={{ left: 0, position: 'absolute', top: 0 }}
|
|
110
|
+
width={'100%'}
|
|
111
|
+
/>
|
|
112
|
+
</Flexbox>
|
|
113
|
+
</Flexbox>
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export default DeviceCard;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import PageTitle from '@/components/PageTitle';
|
|
7
|
+
|
|
8
|
+
export default memo(() => {
|
|
9
|
+
const { t } = useTranslation('setting');
|
|
10
|
+
return <PageTitle title={t('tab.sync')} />;
|
|
11
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
+
import { Input, InputProps } from 'antd';
|
|
3
|
+
import { FormInstance } from 'antd/es/form/hooks/useForm';
|
|
4
|
+
import { LucideDices } from 'lucide-react';
|
|
5
|
+
import { memo, useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
import { generateRandomRoomName } from '@/app/settings/sync/util';
|
|
9
|
+
|
|
10
|
+
interface ChannelNameInputProps extends Omit<InputProps, 'form'> {
|
|
11
|
+
form: FormInstance;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ChannelNameInput = memo<ChannelNameInputProps>(({ form, ...res }) => {
|
|
15
|
+
const { t } = useTranslation('setting');
|
|
16
|
+
const [loading, setLoading] = useState(false);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Input
|
|
20
|
+
placeholder={t('sync.webrtc.channelName.placeholder')}
|
|
21
|
+
suffix={
|
|
22
|
+
<ActionIcon
|
|
23
|
+
active
|
|
24
|
+
icon={LucideDices}
|
|
25
|
+
loading={loading}
|
|
26
|
+
onClick={async () => {
|
|
27
|
+
setLoading(true);
|
|
28
|
+
const name = await generateRandomRoomName();
|
|
29
|
+
setLoading(false);
|
|
30
|
+
form.setFieldValue(['sync', 'webrtc', 'channelName'], name);
|
|
31
|
+
form.setFieldValue(['sync', 'webrtc', 'enabled'], false);
|
|
32
|
+
form.submit();
|
|
33
|
+
}}
|
|
34
|
+
size={'small'}
|
|
35
|
+
style={{
|
|
36
|
+
marginRight: -4,
|
|
37
|
+
}}
|
|
38
|
+
title={t('sync.webrtc.channelName.shuffle')}
|
|
39
|
+
/>
|
|
40
|
+
}
|
|
41
|
+
{...res}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export default ChannelNameInput;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { SiWebrtc } from '@icons-pack/react-simple-icons';
|
|
4
|
+
import { Form, type ItemGroup, Tooltip } from '@lobehub/ui';
|
|
5
|
+
import { Form as AntForm, Input, Switch, Typography } from 'antd';
|
|
6
|
+
import { memo } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { Flexbox } from 'react-layout-kit';
|
|
9
|
+
|
|
10
|
+
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
11
|
+
import SyncStatusInspector from '@/features/SyncStatusInspector';
|
|
12
|
+
import { useGlobalStore } from '@/store/global';
|
|
13
|
+
|
|
14
|
+
import { useSyncSettings } from '../../hooks/useSyncSettings';
|
|
15
|
+
import ChannelNameInput from './ChannelNameInput';
|
|
16
|
+
|
|
17
|
+
type SettingItemGroup = ItemGroup;
|
|
18
|
+
|
|
19
|
+
const WebRTC = memo(() => {
|
|
20
|
+
const { t } = useTranslation('setting');
|
|
21
|
+
const [form] = AntForm.useForm();
|
|
22
|
+
|
|
23
|
+
const [setSettings] = useGlobalStore((s) => [s.setSettings]);
|
|
24
|
+
|
|
25
|
+
useSyncSettings(form);
|
|
26
|
+
|
|
27
|
+
const channelName = AntForm.useWatch(['sync', 'webrtc', 'channelName'], form);
|
|
28
|
+
|
|
29
|
+
const config: SettingItemGroup = {
|
|
30
|
+
children: [
|
|
31
|
+
{
|
|
32
|
+
children: <ChannelNameInput form={form} />,
|
|
33
|
+
desc: t('sync.webrtc.channelName.desc'),
|
|
34
|
+
label: t('sync.webrtc.channelName.title'),
|
|
35
|
+
name: ['sync', 'webrtc', 'channelName'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
children: (
|
|
39
|
+
<Input.Password
|
|
40
|
+
autoComplete={'nw-password'}
|
|
41
|
+
placeholder={t('sync.webrtc.channelPassword.placeholder')}
|
|
42
|
+
/>
|
|
43
|
+
),
|
|
44
|
+
desc: t('sync.webrtc.channelPassword.desc'),
|
|
45
|
+
label: t('sync.webrtc.channelPassword.title'),
|
|
46
|
+
name: ['sync', 'webrtc', 'channelPassword'],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
children: !channelName ? (
|
|
50
|
+
<Tooltip title={t('sync.webrtc.enabled.invalid')}>
|
|
51
|
+
<Switch disabled />
|
|
52
|
+
</Tooltip>
|
|
53
|
+
) : (
|
|
54
|
+
<Switch />
|
|
55
|
+
// <SyncSwitch />
|
|
56
|
+
),
|
|
57
|
+
|
|
58
|
+
label: t('sync.webrtc.enabled.title'),
|
|
59
|
+
minWidth: undefined,
|
|
60
|
+
name: ['sync', 'webrtc', 'enabled'],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
extra: (
|
|
64
|
+
<div
|
|
65
|
+
onClick={(e) => {
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
<SyncStatusInspector hiddenActions hiddenEnableGuide />
|
|
70
|
+
</div>
|
|
71
|
+
),
|
|
72
|
+
title: (
|
|
73
|
+
<Flexbox gap={8} horizontal>
|
|
74
|
+
{/* @ts-ignore */}
|
|
75
|
+
<SiWebrtc />
|
|
76
|
+
<Flexbox align={'baseline'} gap={8} horizontal>
|
|
77
|
+
{t('sync.webrtc.title')}
|
|
78
|
+
<Typography.Text style={{ fontWeight: 'normal' }} type={'secondary'}>
|
|
79
|
+
{t('sync.webrtc.desc')}
|
|
80
|
+
</Typography.Text>
|
|
81
|
+
</Flexbox>
|
|
82
|
+
</Flexbox>
|
|
83
|
+
),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Form
|
|
88
|
+
form={form}
|
|
89
|
+
items={[config]}
|
|
90
|
+
onFinish={setSettings}
|
|
91
|
+
onValuesChange={setSettings}
|
|
92
|
+
{...FORM_STYLE}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export default WebRTC;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/* stylelint-disable */
|
|
2
|
+
.wrapper {
|
|
3
|
+
--hue: 223;
|
|
4
|
+
--off-hue: 3;
|
|
5
|
+
--on-hue1: 123;
|
|
6
|
+
--on-hue2: 168;
|
|
7
|
+
--fg: hsl(var(--hue), 10%, 90%);
|
|
8
|
+
--primary: hsl(var(--hue), 90%, 50%);
|
|
9
|
+
--trans-dur: 0.6s;
|
|
10
|
+
--trans-timing: cubic-bezier(0.65, 0, 0.35, 1);
|
|
11
|
+
|
|
12
|
+
font-size: 14px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.switch,
|
|
16
|
+
.switch__input {
|
|
17
|
+
-webkit-tap-highlight-color: #0000;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.switch {
|
|
21
|
+
position: relative;
|
|
22
|
+
|
|
23
|
+
display: block;
|
|
24
|
+
|
|
25
|
+
width: 5em;
|
|
26
|
+
height: 3em;
|
|
27
|
+
margin: auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.switch__base-outer,
|
|
31
|
+
.switch__base-inner {
|
|
32
|
+
position: absolute;
|
|
33
|
+
display: block;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.switch__base-outer {
|
|
37
|
+
top: 0.125em;
|
|
38
|
+
left: 0.125em;
|
|
39
|
+
|
|
40
|
+
width: 4.75em;
|
|
41
|
+
height: 2.75em;
|
|
42
|
+
|
|
43
|
+
border-radius: 1.25em;
|
|
44
|
+
box-shadow:
|
|
45
|
+
-0.125em -0.125em 0.25em hsl(var(--hue), 10%, 30%),
|
|
46
|
+
0.125em 0.125em 0.125em hsl(var(--hue), 10%, 30%) inset,
|
|
47
|
+
0.125em 0.125em 0.25em hsl(0deg, 0%, 0%),
|
|
48
|
+
-0.125em -0.125em 0.125em hsl(var(--hue), 10%, 5%) inset;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.switch__base-inner {
|
|
52
|
+
top: 0.375em;
|
|
53
|
+
left: 0.375em;
|
|
54
|
+
|
|
55
|
+
width: 4.25em;
|
|
56
|
+
height: 2.25em;
|
|
57
|
+
|
|
58
|
+
border-radius: 1.125em;
|
|
59
|
+
box-shadow:
|
|
60
|
+
-0.25em -0.25em 0.25em hsl(var(--hue), 10%, 30%) inset,
|
|
61
|
+
0.0625em 0.0625em 0.125em hsla(var(--hue), 10%, 30%),
|
|
62
|
+
0.125em 0.25em 0.25em hsl(var(--hue), 10%, 5%) inset,
|
|
63
|
+
-0.0625em -0.0625em 0.125em hsla(var(--hue), 10%, 5%);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.switch__base-neon {
|
|
67
|
+
position: absolute;
|
|
68
|
+
top: 0;
|
|
69
|
+
left: 0;
|
|
70
|
+
|
|
71
|
+
overflow: visible;
|
|
72
|
+
display: block;
|
|
73
|
+
|
|
74
|
+
width: 100%;
|
|
75
|
+
height: auto;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.switch__base-neon path {
|
|
79
|
+
stroke-dasharray: 0 104.26 0;
|
|
80
|
+
transition: stroke-dasharray var(--trans-dur) var(--trans-timing);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.switch__input {
|
|
84
|
+
position: relative;
|
|
85
|
+
|
|
86
|
+
width: 100%;
|
|
87
|
+
height: 100%;
|
|
88
|
+
appearance: none;
|
|
89
|
+
outline: transparent;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.switch__input::before {
|
|
93
|
+
content: '';
|
|
94
|
+
|
|
95
|
+
position: absolute;
|
|
96
|
+
inset: -0.125em;
|
|
97
|
+
|
|
98
|
+
display: block;
|
|
99
|
+
|
|
100
|
+
border-radius: 0.125em;
|
|
101
|
+
box-shadow: 0 0 0 0.125em hsla(var(--hue), 90%, 50%, 0%);
|
|
102
|
+
|
|
103
|
+
transition: box-shadow 0.15s linear;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.switch__input:focus-visible::before {
|
|
107
|
+
box-shadow: 0 0 0 0.125em var(--primary);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.switch__knob,
|
|
111
|
+
.switch__knob-container {
|
|
112
|
+
position: absolute;
|
|
113
|
+
display: block;
|
|
114
|
+
border-radius: 1em;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.switch__knob {
|
|
118
|
+
width: 2em;
|
|
119
|
+
height: 2em;
|
|
120
|
+
|
|
121
|
+
background-color: hsl(var(--hue), 10%, 15%);
|
|
122
|
+
background-image: radial-gradient(
|
|
123
|
+
88% 88% at 50% 50%,
|
|
124
|
+
hsl(var(--hue), 10%, 20%) 47%,
|
|
125
|
+
hsla(var(--hue), 10%, 20%, 0%) 50%
|
|
126
|
+
),
|
|
127
|
+
radial-gradient(
|
|
128
|
+
88% 88% at 47% 47%,
|
|
129
|
+
hsl(var(--hue), 10%, 85%) 45%,
|
|
130
|
+
hsla(var(--hue), 10%, 85%, 0%) 50%
|
|
131
|
+
),
|
|
132
|
+
radial-gradient(
|
|
133
|
+
65% 70% at 40% 60%,
|
|
134
|
+
hsl(var(--hue), 10%, 20%) 46%,
|
|
135
|
+
hsla(var(--hue), 10%, 20%, 0%) 50%
|
|
136
|
+
);
|
|
137
|
+
box-shadow:
|
|
138
|
+
-0.0625em -0.0625em 0.0625em hsl(var(--hue), 10%, 15%) inset,
|
|
139
|
+
-0.125em -0.125em 0.0625em hsl(var(--hue), 10%, 5%) inset,
|
|
140
|
+
0.75em 0.25em 0.125em hsla(0deg, 0%, 0%, 80%);
|
|
141
|
+
|
|
142
|
+
transition: transform var(--trans-dur) var(--trans-timing);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.switch__knob-container {
|
|
146
|
+
top: 0.5em;
|
|
147
|
+
left: 0.5em;
|
|
148
|
+
|
|
149
|
+
overflow: hidden;
|
|
150
|
+
|
|
151
|
+
width: 4em;
|
|
152
|
+
height: 2em;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.switch__knob-neon {
|
|
156
|
+
display: block;
|
|
157
|
+
width: 2em;
|
|
158
|
+
height: auto;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.switch__knob-neon circle {
|
|
162
|
+
opacity: 0;
|
|
163
|
+
stroke-dasharray: 0 90.32 0 54.19;
|
|
164
|
+
transition:
|
|
165
|
+
opacity var(--trans-dur) steps(1, end),
|
|
166
|
+
stroke-dasharray var(--trans-dur) var(--trans-timing);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.switch__knob-shadow {
|
|
170
|
+
position: absolute;
|
|
171
|
+
top: 0.5em;
|
|
172
|
+
left: 0.5em;
|
|
173
|
+
|
|
174
|
+
display: block;
|
|
175
|
+
|
|
176
|
+
width: 2em;
|
|
177
|
+
height: 2em;
|
|
178
|
+
|
|
179
|
+
border-radius: 50%;
|
|
180
|
+
box-shadow: 0.125em 0.125em 0.125em hsla(0deg, 0%, 0%, 90%);
|
|
181
|
+
|
|
182
|
+
transition: transform var(--trans-dur) var(--trans-timing);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.switch__led {
|
|
186
|
+
position: absolute;
|
|
187
|
+
top: 0;
|
|
188
|
+
left: 0;
|
|
189
|
+
|
|
190
|
+
display: block;
|
|
191
|
+
|
|
192
|
+
width: 0.25em;
|
|
193
|
+
height: 0.25em;
|
|
194
|
+
|
|
195
|
+
background-color: hsl(var(--off-hue), 90%, 70%);
|
|
196
|
+
border-radius: 50%;
|
|
197
|
+
box-shadow:
|
|
198
|
+
0 -0.0625em 0.0625em hsl(var(--off-hue), 90%, 40%) inset,
|
|
199
|
+
0 0 0.125em hsla(var(--off-hue), 90%, 70%, 30%),
|
|
200
|
+
0 0 0.125em hsla(var(--off-hue), 90%, 70%, 30%),
|
|
201
|
+
0.125em 0.125em 0.125em hsla(0deg, 0%, 0%, 50%);
|
|
202
|
+
|
|
203
|
+
transition:
|
|
204
|
+
background-color var(--trans-dur) var(--trans-timing),
|
|
205
|
+
box-shadow var(--trans-dur) var(--trans-timing);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.switch__text {
|
|
209
|
+
position: absolute;
|
|
210
|
+
overflow: hidden;
|
|
211
|
+
width: 1px;
|
|
212
|
+
height: 1px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.switch__input:checked ~ .switch__led {
|
|
216
|
+
background-color: hsl(var(--on-hue1), 90%, 70%);
|
|
217
|
+
box-shadow:
|
|
218
|
+
0 -0.0625em 0.0625em hsl(var(--on-hue1), 90%, 40%) inset,
|
|
219
|
+
0 -0.125em 0.125em hsla(var(--on-hue1), 90%, 70%, 30%),
|
|
220
|
+
0 0.125em 0.125em hsla(var(--on-hue1), 90%, 70%, 30%),
|
|
221
|
+
0.125em 0.125em 0.125em hsla(0deg, 0%, 0%, 50%);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.switch__input:checked ~ .switch__base-neon path {
|
|
225
|
+
stroke-dasharray: 52.13 0 52.13;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.switch__input:checked ~ .switch__knob-shadow,
|
|
229
|
+
.switch__input:checked ~ .switch__knob-container .switch__knob {
|
|
230
|
+
transform: translateX(100%);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.switch__input:checked ~ .switch__knob-container .switch__knob-neon circle {
|
|
234
|
+
opacity: 1;
|
|
235
|
+
stroke-dasharray: 45.16 0 45.16 54.19;
|
|
236
|
+
transition-timing-function: steps(1, start), var(--trans-timing);
|
|
237
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
|
|
3
|
+
import './index.css';
|
|
4
|
+
|
|
5
|
+
interface SyncSwitchProps {
|
|
6
|
+
onChange?: (checked: boolean) => void;
|
|
7
|
+
value?: boolean;
|
|
8
|
+
}
|
|
9
|
+
const SyncSwitch = memo<SyncSwitchProps>(({ value, onChange }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className={'wrapper'}>
|
|
12
|
+
<label className="switch">
|
|
13
|
+
<input
|
|
14
|
+
checked={value}
|
|
15
|
+
className="switch__input"
|
|
16
|
+
onChange={(e) => {
|
|
17
|
+
onChange?.(e.target.checked);
|
|
18
|
+
}}
|
|
19
|
+
role="switch"
|
|
20
|
+
type="checkbox"
|
|
21
|
+
/>
|
|
22
|
+
<span className="switch__base-outer"></span>
|
|
23
|
+
<span className="switch__base-inner"></span>
|
|
24
|
+
<svg className="switch__base-neon" height="24px" viewBox="0 0 40 24" width="40px">
|
|
25
|
+
<defs>
|
|
26
|
+
<filter id="switch-glow">
|
|
27
|
+
<feGaussianBlur result="coloredBlur" stdDeviation="1"></feGaussianBlur>
|
|
28
|
+
<feMerge>
|
|
29
|
+
<feMergeNode in="coloredBlur"></feMergeNode>
|
|
30
|
+
<feMergeNode in="SourceGraphic"></feMergeNode>
|
|
31
|
+
</feMerge>
|
|
32
|
+
</filter>
|
|
33
|
+
<linearGradient id="switch-gradient1" x1="0" x2="1" y1="0" y2="0">
|
|
34
|
+
<stop offset="0%" stopColor="hsl(var(--on-hue1),90%,70%)" />
|
|
35
|
+
<stop offset="100%" stopColor="hsl(var(--on-hue2),90%,70%)" />
|
|
36
|
+
</linearGradient>
|
|
37
|
+
<linearGradient id="switch-gradient2" x1="0.7" x2="0.3" y1="0" y2="1">
|
|
38
|
+
<stop offset="25%" stopColor="hsla(var(--on-hue1),90%,70%,0)" />
|
|
39
|
+
<stop offset="50%" stopColor="hsla(var(--on-hue1),90%,70%,0.3)" />
|
|
40
|
+
<stop offset="100%" stopColor="hsla(var(--on-hue2),90%,70%,0.3)" />
|
|
41
|
+
</linearGradient>
|
|
42
|
+
</defs>
|
|
43
|
+
<path
|
|
44
|
+
d="m.5,12C.5,5.649,5.649.5,12,.5h16c6.351,0,11.5,5.149,11.5,11.5s-5.149,11.5-11.5,11.5H12C5.649,23.5.5,18.351.5,12Z"
|
|
45
|
+
fill="none"
|
|
46
|
+
filter="url(#switch-glow)"
|
|
47
|
+
stroke="url(#switch-gradient1)"
|
|
48
|
+
strokeDasharray="0 104.26 0"
|
|
49
|
+
strokeDashoffset="0.01"
|
|
50
|
+
strokeLinecap="round"
|
|
51
|
+
strokeWidth="1"
|
|
52
|
+
/>
|
|
53
|
+
</svg>
|
|
54
|
+
<span className="switch__knob-shadow"></span>
|
|
55
|
+
<span className="switch__knob-container">
|
|
56
|
+
<span className="switch__knob">
|
|
57
|
+
<svg className="switch__knob-neon" height="48px" viewBox="0 0 48 48" width="48px">
|
|
58
|
+
<circle
|
|
59
|
+
cx="24"
|
|
60
|
+
cy="24"
|
|
61
|
+
fill="none"
|
|
62
|
+
r="23"
|
|
63
|
+
stroke="url(#switch-gradient2)"
|
|
64
|
+
strokeDasharray="0 90.32 0 54.19"
|
|
65
|
+
strokeLinecap="round"
|
|
66
|
+
strokeWidth="1"
|
|
67
|
+
transform="rotate(-112.5,24,24)"
|
|
68
|
+
/>
|
|
69
|
+
</svg>
|
|
70
|
+
</span>
|
|
71
|
+
</span>
|
|
72
|
+
<span className="switch__led"></span>
|
|
73
|
+
<span className="switch__text">Power</span>
|
|
74
|
+
</label>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export default SyncSwitch;
|