{
28 | props.onSelect && props.onSelect(str);
29 | }} className={'app_cursor_pointer'} style={{
30 | background: str,
31 | width: '100%',
32 | height: '2.125rem',
33 | borderRadius: '.4rem'
34 | }}/>
35 | );
36 | };
37 |
38 | useEffect((): void => {
39 | Request.getColors({type: 'background'})
40 | .then((res): void => setColors(res.data.rows))
41 | .finally((): void => {
42 | setLoading(false);
43 | });
44 | }, []);
45 |
46 | return (
47 |
48 | {
49 | colors.length == 0 ?
50 |
51 | :
52 |
57 | {
58 | colors.map((i: IColorType): React.JSX.Element => {
59 | return (
60 |
61 | {
62 | getBackgroundItem(i.value)
63 | }
64 |
65 | );
66 | })
67 | }
68 |
69 | }
70 |
71 | );
72 | };
73 |
74 | export default SelectBackground;
--------------------------------------------------------------------------------
/web_app/src/components/ColorConfig/SelectTheme.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from "react";
2 | import {EmptyState, Grid, Loading} from "@hi-ui/hiui";
3 | import Request from "../../lib/Request";
4 | import {ReactState} from "../../types/ReactTypes";
5 | import {useTranslation} from "react-i18next";
6 |
7 | export interface ISelectThemeProps {
8 | span?: number;
9 | readonly onChange: (value: Array
) => void;
10 | }
11 |
12 | interface IColorType {
13 | id: string;
14 | type: string;
15 | value: string;
16 | }
17 |
18 | function SelectTheme(props: ISelectThemeProps): React.JSX.Element {
19 | const {
20 | span = 12,
21 | onChange
22 | }: ISelectThemeProps = props;
23 | const [colors, setColors]: ReactState> = useState>([]);
24 | const [loading, setLoading]: ReactState = useState(true);
25 | const [currentThemeId, setCurrentThemeId]: ReactState = useState('');
26 | const {t} = useTranslation();
27 |
28 | useEffect((): void => {
29 | Request.getColors({type: 'theme'})
30 | .then((res): void => setColors(res.data.rows))
31 | .finally((): void => {
32 | setLoading(false);
33 | });
34 | }, []);
35 |
36 | return (
37 | <>
38 |
39 |
48 | {
49 | colors.length === 0 ?
50 | :
53 | colors.map((i: IColorType): React.JSX.Element => {
54 | return (
55 |
56 | {
58 | if (currentThemeId === i.id) return;
59 | setCurrentThemeId(i.id);
60 | onChange && onChange(JSON.parse(i.value));
61 | }}>
62 | {
63 | JSON.parse(i.value).map((j: string, k: number): React.JSX.Element => {
64 | return (
65 |
66 | );
67 | })
68 | }
69 |
70 |
71 | );
72 | })
73 | }
74 |
75 |
76 | >
77 | );
78 | }
79 |
80 | export default SelectTheme;
--------------------------------------------------------------------------------
/web_app/src/components/ColorConfig/SingleColorPicker.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import {Grid} from "@hi-ui/hiui";
3 | import ColorPicker from "../ColorPicker";
4 | import {ReactState} from "../../types/ReactTypes";
5 |
6 | export interface ISingleColorPickerProps {
7 | value?: string;
8 | readonly onChange?: (value: string) => void;
9 | }
10 |
11 | const SingleColorPicker = (props: ISingleColorPickerProps): React.JSX.Element => {
12 | const {
13 | value,
14 | onChange
15 | }: ISingleColorPickerProps = props;
16 |
17 | const [color, setColor]: ReactState = useState(value ?? '');
18 |
19 | return (
20 |
27 |
33 | {
35 | setColor(e);
36 | onChange && onChange(e);
37 | }}
38 | value={color}
39 | style={{
40 | width: '100%',
41 | height: '3.125rem'
42 | }}
43 | />
44 |
45 |
46 | );
47 | };
48 |
49 | export default SingleColorPicker;
50 |
--------------------------------------------------------------------------------
/web_app/src/components/ColorPicker/index.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef} from "react";
2 |
3 | export interface IColorPickerProps {
4 | value: string;
5 | onChange?: (value: string) => void;
6 | style?: React.CSSProperties;
7 | }
8 |
9 | const ColorPicker = (props: IColorPickerProps): React.JSX.Element => {
10 | const {value, onChange}: IColorPickerProps = props;
11 | const ref: React.RefObject = useRef(null);
12 |
13 | useEffect((): void => {
14 | if (ref.current)
15 | ref.current.value = props.value;
16 | }, [props]);
17 |
18 | return (
19 | <>
20 | ): void => {
26 | onChange && onChange(e.target.value);
27 | }}
28 | />
29 | >
30 | );
31 | };
32 |
33 | export default ColorPicker;
34 |
--------------------------------------------------------------------------------
/web_app/src/components/GlobalComponent/components/SwitchLang.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from "react";
2 | import {Select, SelectOption} from "@hi-ui/hiui";
3 | import MyStorage from "../../../lib/Storage";
4 | import {useTranslation} from "react-i18next";
5 |
6 | // const langs = ['zh-CN', 'h-TW', 'ja', 'ko'];
7 |
8 | const SwitchLang = (): React.JSX.Element => {
9 | const currentLang = MyStorage.get('lang') ?? 'zh-CN';
10 | const [lang, setLang] = useState(currentLang);
11 | const {i18n} = useTranslation();
12 | const params = new URLSearchParams(location.search.substring(1));
13 |
14 | useEffect((): void => {
15 | setLang(params.get('lang') ?? currentLang);
16 | MyStorage.set('lang', currentLang);
17 | }, []);
18 |
19 | useEffect((): void => {
20 | i18n.changeLanguage(lang);
21 | }, [lang]);
22 |
23 | return (
24 |
44 | );
45 | };
46 |
47 | export default SwitchLang;
48 |
--------------------------------------------------------------------------------
/web_app/src/components/GlobalComponent/components/TDisplayTemplate.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import {ReactState} from "../../../types/ReactTypes";
3 |
4 | type TChildren = React.JSX.Element | React.JSX.Element[] | string[] | string | number | number[];
5 |
6 | interface TDisplayTemplateProps {
7 | show: boolean;
8 | children: TChildren;
9 | style?: React.CSSProperties | undefined;
10 | }
11 |
12 | const TDisplayTemplate = (props: TDisplayTemplateProps): React.JSX.Element => {
13 | const {show, children, style = {}} = props;
14 | const [catchTemplate]: ReactState = useState(children);
15 |
16 | return (
17 |
22 | {catchTemplate}
23 |
24 | );
25 | };
26 |
27 | export default TDisplayTemplate;
--------------------------------------------------------------------------------
/web_app/src/components/GlobalComponent/components/THTMLTemplate.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef} from "react";
2 |
3 | export interface ITHTMLTemplateProps {
4 | content: string;
5 | }
6 |
7 | const THTMLTemplate = (props: ITHTMLTemplateProps): React.JSX.Element => {
8 | const {content}: ITHTMLTemplateProps = props;
9 | const mainElRef: React.RefObject = useRef(null);
10 |
11 | useEffect((): void => {
12 | if (mainElRef.current)
13 | mainElRef.current.innerHTML = content;
14 | }, [props]);
15 |
16 | return (
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default THTMLTemplate;
--------------------------------------------------------------------------------
/web_app/src/components/GlobalComponent/index.ts:
--------------------------------------------------------------------------------
1 | import SwitchTheme from "./components/SwitchTheme";
2 | import TDisplayTemplate from "./components/TDisplayTemplate";
3 | import THTMLTemplate from "./components/THTMLTemplate";
4 |
5 | const GlobalComponent = {
6 | SwitchTheme,
7 | TDisplayTemplate,
8 | THTMLTemplate
9 | };
10 |
11 | export default GlobalComponent;
--------------------------------------------------------------------------------
/web_app/src/components/Header/HomeHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, {useRef} from "react";
2 | import {useTranslation} from "react-i18next";
3 | import AppConfig from "../../config/AppConfig";
4 | import GlobalComponent from "../GlobalComponent";
5 | import {Button, Grid} from "@hi-ui/hiui";
6 | import SelectFile, {ISelectFileRef} from "../SelectFile";
7 | import SwitchLang from "../GlobalComponent/components/SwitchLang";
8 | import YExtendTemplate from "../YExtendTemplate";
9 | import Resources, {IResourcesRef} from "../Resources";
10 | import About, {IAboutRef} from "../About";
11 |
12 | const HomeHeader = (): React.JSX.Element => {
13 | const selectFileRef: React.MutableRefObject = useRef(null);
14 | const resourcesRef: React.MutableRefObject = useRef(null);
15 | const aboutRef: React.MutableRefObject = useRef(null);
16 | const {t} = useTranslation();
17 |
18 | return (
19 |
20 |
23 |
26 |
27 |
28 | {
31 | aboutRef.current?.open?.();
32 | }}
33 | >
34 | {AppConfig.appName}
35 |
36 |
37 |
38 |
39 |
40 |
43 |
49 |
52 |
53 |
54 |
55 |
64 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | export default HomeHeader;
82 |
--------------------------------------------------------------------------------
/web_app/src/components/Header/index.ts:
--------------------------------------------------------------------------------
1 | import DesignHeader from "./DesignHeader";
2 | import HomeHeader from "./HomeHeader";
3 |
4 | export default {
5 | Design: DesignHeader,
6 | Home: HomeHeader
7 | };
--------------------------------------------------------------------------------
/web_app/src/components/ResetButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export interface IResetButtonProps {
4 | onClick?: () => void;
5 | }
6 |
7 | function ResetButton(props: IResetButtonProps): React.JSX.Element {
8 |
9 |
10 | return (
11 | {
13 | props.onClick && props.onClick();
14 | }
15 | }>
16 |
21 |
22 | );
23 | }
24 |
25 | export default ResetButton;
--------------------------------------------------------------------------------
/web_app/src/components/Resources/VideoPlayer.tsx:
--------------------------------------------------------------------------------
1 | import React, {useImperativeHandle, useState} from "react";
2 | import {Loading, Modal} from "@hi-ui/hiui";
3 | import {ReactState} from "../../types/ReactTypes";
4 | import YExtendTemplate from "../YExtendTemplate";
5 | import {useTranslation} from "react-i18next";
6 |
7 | export interface IVideoPlayerRef {
8 | play: (path: string) => void;
9 | }
10 |
11 | const VideoPlayer: React.ForwardRefExoticComponent> = React.forwardRef((_props: React.RefAttributes, ref: React.ForwardedRef): React.JSX.Element => {
12 | const [visible, setVisible]: ReactState = useState(false);
13 | const [videoPath, setVideoPath]: ReactState = useState('');
14 | const [loading, setLoading]: ReactState = useState(true);
15 | const {t} = useTranslation();
16 |
17 | const play = (path: string): void => {
18 | setVisible(true);
19 | setLoading(true);
20 | setVideoPath(path);
21 | };
22 |
23 | useImperativeHandle(ref, (): IVideoPlayerRef => ({
24 | play
25 | }));
26 |
27 | return (
28 | {
34 | setVisible(false);
35 | }}
36 | onCancel={(): void => {
37 | setVisible(false);
38 | }}
39 | >
40 |
41 |
42 |
60 |
61 |
62 | );
63 | });
64 |
65 | VideoPlayer.displayName = 'VideoPlayer';
66 |
67 | export default VideoPlayer;
--------------------------------------------------------------------------------
/web_app/src/components/Resources/style.scss:
--------------------------------------------------------------------------------
1 | .hi-v4-loading__mask {
2 | border-radius: 0.75rem 0.75rem 0 0;
3 | }
--------------------------------------------------------------------------------
/web_app/src/components/ServerInfo.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useId, useState} from "react";
2 | import {Button, Card, Descriptions, Modal, Space, Tag} from "@hi-ui/hiui";
3 | import Request from "../lib/Request";
4 | import {useTranslation} from "react-i18next";
5 | import * as Icons from "@hi-ui/icons";
6 |
7 | export interface IServerInfo {
8 | main: {
9 | memory: {
10 | rss: number;
11 | heapTotal: number;
12 | heapUsed: number;
13 | external: number;
14 | arrayBuffers: number;
15 | };
16 | upTime: {
17 | days: number;
18 | hours: number;
19 | minutes: number;
20 | seconds: number;
21 | };
22 | synthesisServicesConnectStatus: boolean;
23 | };
24 | rtVersion: {
25 | node: string;
26 | v8: string;
27 | }
28 | }
29 |
30 | const ServerInfo = (): React.JSX.Element => {
31 | const [serverInfo, setServerInfo] = useState(null);
32 | const [visible, setVisible] = useState(false);
33 | const {t} = useTranslation();
34 | const key: string = useId();
35 |
36 | useEffect(() => {
37 | if (!visible) return;
38 | Request.getServerInfo().then(({data}) => setServerInfo(data as IServerInfo));
39 | }, [visible]);
40 |
41 | const renderTag = (isConnected: boolean): React.JSX.Element =>
42 |
43 | {t(isConnected ? 'connected' : 'notConnected')}
44 | ;
45 |
46 | const open = (): void => setVisible(!visible);
47 |
48 | return (
49 |
50 | open()}/>
51 | open()
58 | }
59 | footer={[
60 |
69 | ]}
70 | >
71 | {serverInfo &&
72 |
73 |
74 |
75 | {serverInfo.main.memory.rss} M
77 | {serverInfo.main.memory.heapTotal} M
79 | {serverInfo.main.memory.heapUsed} M
81 | {serverInfo.main.memory.arrayBuffers} M
83 |
84 |
85 |
86 |
87 | {`${serverInfo.rtVersion.node} / ${serverInfo.rtVersion.v8}`}
89 |
90 |
91 |
92 |
93 |
94 | {renderTag(serverInfo.main.synthesisServicesConnectStatus)}
95 |
96 |
97 |
98 |
99 | }
100 |
101 |
102 | );
103 | };
104 |
105 | export default ServerInfo;
106 |
--------------------------------------------------------------------------------
/web_app/src/components/SyntheticConfig/config.ts:
--------------------------------------------------------------------------------
1 | import {RadioDataItem} from "@hi-ui/radio";
2 |
3 | export const fpsConfigs: Array = [
4 | {id: 30, title: "30"}, {id: 60, title: "60"}
5 | ];
6 |
7 | export const durationConfigs: RadioDataItem[] = [
8 | {id: 5000, title: '5'},
9 | {id: 10000, title: '10'},
10 | {id: 20000, title: '20'},
11 | {id: 30000, title: '30'}
12 | ];
13 |
14 | export const clarityConfigs: RadioDataItem[] = [
15 | {id: '1080P', title: '1080P'},
16 | {id: '2K', title: '2K'},
17 | {id: '4K', title: '4K'}
18 | ];
19 |
--------------------------------------------------------------------------------
/web_app/src/components/TemplateList/style.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/theme";
2 |
3 | .template-list {
4 | width: calc(100vw - (2.875rem * 2));
5 | margin: 1.75rem auto auto;
6 |
7 | .template-list-query {
8 | margin-bottom: 1.75rem;
9 |
10 | .template-list-query-select {
11 | float: right;
12 | }
13 | }
14 |
15 | .template-item {
16 | border: 0;
17 | border-radius: .8rem;
18 | overflow: hidden;
19 |
20 | img {
21 | width: 100%;
22 | height: 100%;
23 | border-radius: .8rem;
24 | transition: all .2s;
25 | }
26 |
27 | .img-active {
28 | transform: scale(1.1);
29 | }
30 |
31 | .template-item-name {
32 | position: absolute;
33 | color: $color-white;
34 | background: linear-gradient(180deg, rgba(255, 254, 254, 0) 0%, rgba(217, 217, 217, 0) 0.01%, rgba(0, 0, 0, 0.5) 100%);
35 | width: 100%;
36 | height: 4.375rem;
37 | flex-shrink: 0;
38 | bottom: 0;
39 |
40 | span {
41 | position: relative;
42 | left: 1.75rem;
43 | top: 1.75rem;
44 | font-size: 1.14rem;
45 | font-style: normal;
46 | font-weight: 500;
47 | line-height: normal;
48 | }
49 | }
50 |
51 | .template-item-mask {
52 | position: absolute;
53 | width: 100%;
54 | height: 100%;
55 | background: #00000060;
56 | top: 0;
57 | animation-duration: .2s !important;
58 |
59 | .template-item-mask-option {
60 | top: 8%;
61 | right: 6%;
62 | justify-content: end;
63 |
64 | .template-item-mask-option-item {
65 | width: 1.375rem;
66 | height: 1.375rem;
67 | background: #FFFFFF33;
68 | border-radius: .5rem;
69 | margin-left: 0.75rem;
70 |
71 | svg {
72 | width: 0.75rem;
73 | height: 0.75rem;
74 | margin-left: 0.3125rem;
75 | }
76 | }
77 |
78 | .template-item-mask-option-item-primary {
79 | background: #0688E5;
80 | }
81 |
82 | .template-item-mask-option-item-danger {
83 | background: #BE1919;
84 | }
85 | }
86 |
87 | .template-item-mask-name {
88 | font-size: 1.125rem;
89 | text-align: center;
90 | color: $color-white;
91 | top: 30%;
92 | animation-duration: .3s;
93 | }
94 |
95 | .template-item-mask-description {
96 | font-size: 0.875rem;
97 | text-align: center;
98 | color: $color-white;
99 | top: 40%;
100 | animation-duration: .3s;
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/web_app/src/components/YExtendTemplate.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | type YExtendTemplateElementType = React.JSX.Element;
4 | type YExtendTemplateChildrenElementType = React.JSX.Element;
5 |
6 | export interface YExtendTemplateProps {
7 | children: YExtendTemplateElementType | YExtendTemplateElementType[];
8 | show?: boolean;
9 | }
10 |
11 | const YExtendTemplate = (props: YExtendTemplateProps): YExtendTemplateChildrenElementType => {
12 | const {
13 | children,
14 | show = true
15 | }: YExtendTemplateProps = props;
16 |
17 | if (!show) return <>>;
18 |
19 | if (Array.isArray(children))
20 | return {children.map(child => child)};
21 |
22 | return children;
23 | };
24 |
25 | export default YExtendTemplate;
26 |
--------------------------------------------------------------------------------
/web_app/src/components/index.ts:
--------------------------------------------------------------------------------
1 | import YExtendTemplate from "./YExtendTemplate";
2 |
3 | export default {
4 | YExtendTemplate
5 | };
6 |
--------------------------------------------------------------------------------
/web_app/src/config/AppConfig.ts:
--------------------------------------------------------------------------------
1 | export type AppConfigStorage = 'local' | 'session';
2 |
3 | const AppConfig = {
4 | appName: 'lmo-Data-Visualization',
5 | appAuthor: 'ayuanlmo',
6 | title: 'lmo-Data-Visualization',
7 | storageOptions: {
8 | namespace: '_lmo_',
9 | storage: 'local' as AppConfigStorage
10 | },
11 | pages: {
12 | welcome: true
13 | },
14 | openSource: {
15 | github: 'https://github.com/ayuanlmo/lmo-data-visualization'
16 | }
17 | } as const;
18 |
19 | export default AppConfig;
20 |
--------------------------------------------------------------------------------
/web_app/src/const/AnimateNames.ts:
--------------------------------------------------------------------------------
1 | const AnimateNames = [
2 | {
3 | name: '晃动效果',
4 | animates: [
5 | 'bounce',
6 | 'flash',
7 | 'rubberBand',
8 | 'shake',
9 | 'headShake'
10 | ]
11 | },
12 | {
13 | name: '弹性缓冲效果',
14 | animates: [
15 | 'bounceIn',
16 | 'bounceOut'
17 | ]
18 | },
19 | {
20 | name: '透明度变化效果',
21 | animates: [
22 | 'fadeIn',
23 | 'fadeInDown',
24 | 'fadeInLeft',
25 | 'fadeInUp',
26 | 'fadeOut'
27 | ]
28 | },
29 | {
30 | name: '翻转效果',
31 | animates: [
32 | 'flip',
33 | 'flipInX',
34 | 'flipInY',
35 | 'flipOutX',
36 | 'flipOutY'
37 | ]
38 | },
39 | {
40 | name: '滑动效果',
41 | animates: [
42 | 'slideInDown',
43 | 'slideInLeft'
44 | ]
45 | },
46 | {
47 | name: '缩放效果',
48 | animates: [
49 | 'zoomIn',
50 | 'zoomInLeft',
51 | 'zoomOut',
52 | 'zoomOutLeft'
53 | ]
54 | }
55 | ] as const;
56 |
57 | export default AnimateNames;
58 |
--------------------------------------------------------------------------------
/web_app/src/const/Message.ts:
--------------------------------------------------------------------------------
1 | export type MessageTypes =
2 | 'UPDATE_COLOR_MODE'
3 | | 'UPDATE_DURATION'
4 | | 'UPDATE_TEXT'
5 | | 'UPDATE_COLOR'
6 | | 'PREVIEW'
7 | | 'UPDATE_DATA'
8 | | 'UPDATE_BACKGROUND_IMAGE'
9 | | 'UPDATE_THEME_COLOR'
10 | | 'UPDATE_ANIMATE_NAME';
11 |
12 |
13 | //更新标题动画
14 | export const UPDATE_ANIMATE_NAME: MessageTypes = 'UPDATE_ANIMATE_NAME';
15 | //更新主题颜色
16 | export const UPDATE_THEME_COLOR: MessageTypes = 'UPDATE_THEME_COLOR';
17 | //更新背景
18 | export const UPDATE_BACKGROUND_IMAGE: MessageTypes = 'UPDATE_BACKGROUND_IMAGE';
19 | //更新数据
20 | export const UPDATE_DATA: MessageTypes = 'UPDATE_DATA';
21 | //预览
22 | export const PREVIEW: MessageTypes = 'PREVIEW';
23 | //更新颜色配置
24 | export const UPDATE_COLOR: MessageTypes = 'UPDATE_COLOR';
25 | //更新文字配置
26 | export const UPDATE_TEXT: MessageTypes = 'UPDATE_TEXT';
27 | //更新持续时间
28 | export const UPDATE_DURATION: MessageTypes = 'UPDATE_DURATION';
29 | //更新颜色类型
30 | export const UPDATE_COLOR_MODE: MessageTypes = 'UPDATE_COLOR_MODE';
31 |
--------------------------------------------------------------------------------
/web_app/src/global.d.ts:
--------------------------------------------------------------------------------
1 | declare interface Window extends Window {
2 | __LMO_APP_CONFIG: Readonly<{
3 | readonly __PLAYER_EL_ID: `__lmo_dv_app_player_${string}`;
4 | readonly __PREVIEWER_EL_ID: `__lmo_dv_app_preview_${string}`;
5 | }>
6 | }
7 |
--------------------------------------------------------------------------------
/web_app/src/i18n/config/koKr.json:
--------------------------------------------------------------------------------
1 | {
2 | "log": "로그",
3 | "resLib": "자료실",
4 | "materialLib": "자료 라이브러리",
5 | "image": "이미지",
6 | "audio": "오디오",
7 | "video": "비디오",
8 | "add": "추가",
9 | "use": "사용",
10 | "return": "돌아가기",
11 | "returnDesc": "이 작업은 현재 템플릿 데이터를 저장하지 않습니다. 계속 하시겠습니까?",
12 | "continue": "계속",
13 | "delete": "삭제",
14 | "edit": "편집",
15 | "save": "저장",
16 | "cancel": "취소",
17 | "confirm": "확인",
18 | "close": "닫기",
19 | "search": "검색",
20 | "clear": "지우기",
21 | "upload": "업로드",
22 | "download": "다운로드",
23 | "input": "입력",
24 | "select": "선택",
25 | "play": "재생",
26 | "pause": "일시 정지",
27 | "noData": "데이터 없음",
28 | "pleaseSelect": "선택해주세요",
29 | "enterNameToStartQuery": "검색을 시작하려면 이름을 입력하세요",
30 | "all": "모두",
31 | "name": "이름",
32 | "type": "유형",
33 | "size": "크기",
34 | "time": "시간",
35 | "deleteConfirm": "삭제 확인?",
36 | "deleteSuccess": "삭제 성공",
37 | "deleteFailTip": "삭제 실패, 다시 시도해주세요",
38 | "editSuccess": "편집 성공",
39 | "editFailTip": "편집 실패, 다시 시도해주세요",
40 | "uploadSuccess": "업로드 성공",
41 | "uploadFailTip": "업로드 실패, 다시 시도해주세요",
42 | "downloadSuccess": "다운로드 성공",
43 | "downloadFailTip": "다운로드 실패, 다시 시도해주세요",
44 | "copyTemplate": "템플릿 복사",
45 | "editTemplate": "템플릿 편집",
46 | "deleteTemplate": "템플릿 삭제",
47 | "saveAsCustomizeTemplate": "사용자 정의 템플릿으로 저장",
48 | "preview": "미리보기",
49 | "synthesis": "합성",
50 | "chartStyle": "차트 스타일",
51 | "synthesisConfig": "합성 설정",
52 | "editData": "데이터 편집",
53 | "gradientColor": "그라데이션 색상",
54 | "themeColor": "테마 색상",
55 | "singleColor": "단일 색상",
56 | "audioConfig": "오디오 설정",
57 | "fullAudio": "전체 오디오",
58 | "pleaseSelectAudio": "오디오를 선택해주세요",
59 | "audioVolume": "오디오 볼륨",
60 | "replace": "대체",
61 | "videoConfig": "비디오 설정",
62 | "videoFps": "FPS(프레임) 비율",
63 | "videoDuration": "시간(초)",
64 | "videoClarity": "해상도",
65 | "renewedOnTime": "즉시 갱신",
66 | "loadLocalData": "로컬 데이터 불러오기",
67 | "templateName": "템플릿 이름",
68 | "defaultTemplate": "기본",
69 | "customizeTemplate": "사용자 정의",
70 | "templateDes": "템플릿 소개",
71 | "categories": "카테고리",
72 | "pleaseInput": "입력해주세요",
73 | "addSubCategories": "하위 카테고리 추가",
74 | "editCategories": "현재 카테고리 편집",
75 | "deleteCategories": "현재 카테고리 삭제",
76 | "dropUpload": "클릭하거나 파일을 여기로 드래그하여 업로드하세요",
77 | "backgroundConfig": "배경 설정",
78 | "themeConfig": "테마 색상 설정",
79 | "display": "표시",
80 | "textContent": "텍스트 내용",
81 | "fontSize": "글꼴 크기",
82 | "fontColor": "글꼴 색상",
83 | "textAlign": "정렬 방식",
84 | "textPosition": "위치",
85 | "addCategories": "카테고리 추가",
86 | "deleteCategoriesError": "현재 카테고리 삭제 실패. 하위 카테고리가 있을 수 있습니다.",
87 | "tip": "팁",
88 | "textAlignLeft": "왼쪽 정렬",
89 | "textAlignCenter": "가운데 정렬",
90 | "textAlignRight": "오른쪽 정렬",
91 | "addSuccess": "추가 성공",
92 | "file": "파일",
93 | "textConfig": "텍스트 설정",
94 | "addCategoriesMaxLength": "카테고리 이름은 16자를 초과할 수 없습니다",
95 | "systemMessage": "시스템 메시지",
96 | "serviceReConnect": "합성 서비스가 다시 연결되었습니다",
97 | "serviceConnectClose": "합성 서비스 연결이 끊어졌으며, 재연결을 시도 중입니다...",
98 | "unknown": "알 수 없음",
99 | "synthesisSuccess": "합성 완료",
100 | "synthesisRead": "합성 준비 완료",
101 | "taskPending": "합성 중...",
102 | "synthesisError": "합성 실패",
103 | "createTaskSuccess": "합성 작업 생성 성공",
104 | "createTask": "합성 작업 생성",
105 | "taskName": "작업 이름",
106 | "saveAsCustomTemplate": "사용자 정의 템플릿으로 저장하시겠습니까?",
107 | "customTemplateName": "사용자 정의 템플릿 이름",
108 | "customTemplateDesc": "사용자 정의 템플릿 설명",
109 | "about": "관하여",
110 | "openSourceComponentLicense": "오픈 소스 구성 요소 라이선스",
111 | "insertRowAbove": "위에 행 삽입",
112 | "insertRowBelow": "아래에 행 삽입",
113 | "insertRowToLeft": "왼쪽에 행 삽입",
114 | "insertRowToRight": "오른쪽에 행 삽입",
115 | "deleteAnEntireLine": "전체 줄 삭제",
116 | "deleteAnEntireColumn": "전체 열 삭제",
117 | "copy": "복사",
118 | "cut": "잘라내기",
119 | "paste": "붙여넣기",
120 | "undo": "실행 취소",
121 | "liveServerNotSupported": "데모 서버는 이 작업을 지원하지 않습니다",
122 | "serverError": "서버 오류",
123 | "sysNotify": "시스템 알림",
124 | "netError": "네트워크 오류로 인해 서버와 연결할 수 없습니다. 나중에 다시 시도해 주세요.",
125 | "socketError": "서버와 연결이 끊어졌습니다. 재시도 중...",
126 | "socketReConnect": "서버와 다시 연결되었습니다",
127 | "createSuccess": "생성 성공",
128 | "getStarted": "즉시 시작",
129 | "serverInfo": "서버 정보",
130 | "memory": "메모리",
131 | "appUseMemory": "응용 프로그램 메모리 사용량",
132 | "version": "버전",
133 | "heapTotal": "힙 총합",
134 | "heapUsed": "힙 사용량",
135 | "arrayBuffers": "배열 버퍼 크기",
136 | "status": "상태",
137 | "synthesisServicesConnectStatus": "합성 서버 연결 상태",
138 | "connected": "연결됨",
139 | "notConnected": "연결되지 않음"
140 | }
141 |
--------------------------------------------------------------------------------
/web_app/src/i18n/config/zhCn.json:
--------------------------------------------------------------------------------
1 | {
2 | "log": "日志",
3 | "resLib": "资源库",
4 | "materialLib": "素材库",
5 | "image": "图片",
6 | "audio": "音频",
7 | "video": "视频",
8 | "add": "添加",
9 | "use": "使用",
10 | "return": "返回",
11 | "returnDesc": "此操作将不会保存您当前模板数据,是否继续?",
12 | "continue": "继续",
13 | "delete": "删除",
14 | "edit": "编辑",
15 | "save": "保存",
16 | "cancel": "取消",
17 | "confirm": "确认",
18 | "close": "关闭",
19 | "search": "搜索",
20 | "clear": "清空",
21 | "upload": "上传",
22 | "download": "下载",
23 | "input": "输入",
24 | "select": "选择",
25 | "play": "播放",
26 | "pause": "暂停",
27 | "noData": "暂无数据",
28 | "pleaseSelect": "请选择",
29 | "enterNameToStartQuery": "输入名称开始查询",
30 | "all": "全部",
31 | "name": "名称",
32 | "type": "类型",
33 | "size": "大小",
34 | "time": "时间",
35 | "deleteConfirm": "确认删除?",
36 | "deleteSuccess": "删除成功",
37 | "deleteFailTip": "删除失败,请重试",
38 | "editSuccess": "编辑成功",
39 | "editFailTip": "编辑失败,请重试",
40 | "uploadSuccess": "上传成功",
41 | "uploadFailTip": "上传失败,请重试",
42 | "downloadSuccess": "下载成功",
43 | "downloadFailTip": "下载失败,请重试",
44 | "copyTemplate": "复制模板",
45 | "editTemplate": "编辑模板",
46 | "deleteTemplate": "删除模板",
47 | "saveAsCustomizeTemplate": "保存为自定义模板",
48 | "preview": "预览",
49 | "synthesis": "合成",
50 | "chartStyle": "图表样式",
51 | "synthesisConfig": "合成配置",
52 | "editData": "编辑数据",
53 | "gradientColor": "渐变色",
54 | "themeColor": "主题颜色",
55 | "singleColor": "单一色",
56 | "audioConfig": "音频配置",
57 | "fullAudio": "完整音频",
58 | "pleaseSelectAudio": "请选择音频",
59 | "audioVolume": "音量大小",
60 | "replace": "替换",
61 | "videoConfig": "视频配置",
62 | "videoFps": "码率(帧)",
63 | "videoDuration": "时间(秒)",
64 | "videoClarity": "清晰度",
65 | "renewedOnTime": "即时更新",
66 | "loadLocalData": "加载本地数据",
67 | "templateName": "模板名称",
68 | "defaultTemplate": "默认",
69 | "customizeTemplate": "自定义",
70 | "templateDes": "模板介绍",
71 | "categories": "分类",
72 | "pleaseInput": "请输入",
73 | "addSubCategories": "新建子分类",
74 | "editCategories": "编辑当前分类",
75 | "deleteCategories": "删除当前分类",
76 | "dropUpload": "点击或将文件拖拽至此上传",
77 | "backgroundConfig": "背景配置",
78 | "themeConfig": "主题色配置",
79 | "display": "显示",
80 | "textContent": "文字内容",
81 | "fontSize": "字体大小",
82 | "fontColor": "字体颜色",
83 | "textAlign": "对齐方式",
84 | "textPosition": "位置",
85 | "addCategories": "新增分类",
86 | "deleteCategoriesError": "删除当前分类失败,可能存在子分类",
87 | "tip": "提示",
88 | "textAlignLeft": "居左",
89 | "textAlignCenter": "居中",
90 | "textAlignRight": "居右",
91 | "addSuccess": "新增成功",
92 | "file": "文件",
93 | "textConfig": "文字配置",
94 | "addCategoriesMaxLength": "分类名称不可超过16个字符",
95 | "systemMessage": "系统消息",
96 | "serviceReConnect": "合成服务已经重新连接",
97 | "serviceConnectClose": "合成服务连接丢失,正在尝试重连...",
98 | "unknown": "未知",
99 | "synthesisSuccess": "合成完毕",
100 | "synthesisRead": "合成就绪",
101 | "taskPending": "正在合成中...",
102 | "synthesisError": "合成失败",
103 | "createTaskSuccess": "创建合成任务成功",
104 | "createTask": "创建合成任务",
105 | "taskName": "任务名称",
106 | "saveAsCustomTemplate": "是否保存为自定义模板",
107 | "customTemplateName": "自定义模板名称",
108 | "customTemplateDesc": "自定义模板介绍",
109 | "about": "关于",
110 | "openSourceComponentLicense": "开源组件许可",
111 | "insertRowAbove": "在上方插入行",
112 | "insertRowBelow": "在下方插入行",
113 | "insertRowToLeft": "在左侧插入行",
114 | "insertRowToRight": "在右侧插入行",
115 | "deleteAnEntireLine": "删除整行",
116 | "deleteAnEntireColumn": "删除整列",
117 | "copy": "复制",
118 | "cut": "剪切",
119 | "paste": "粘贴",
120 | "undo": "撤销",
121 | "liveServerNotSupported": "演示服务器不支持此操作",
122 | "serverError": "服务器错误",
123 | "sysNotify": "系統通知",
124 | "netError": "网络异常,无法与服务器取得联系,请稍后再试。",
125 | "socketError": "与服务器断开连接,正在重试...",
126 | "socketReConnect": "已重新与服务器连接",
127 | "createSuccess": "创建成功",
128 | "getStarted": "立即开始",
129 | "serverInfo": "服务器信息",
130 | "memory": "内存",
131 | "appUseMemory": "程序内存用量",
132 | "version": "版本",
133 | "heapTotal": "堆总计",
134 | "heapUsed": "堆已使用",
135 | "arrayBuffers": "缓冲区大小",
136 | "status": "状态",
137 | "synthesisServicesConnectStatus": "合成服务器连接状态",
138 | "connected": "已连接",
139 | "notConnected": "未连接"
140 | }
141 |
--------------------------------------------------------------------------------
/web_app/src/i18n/config/zhTw.json:
--------------------------------------------------------------------------------
1 | {
2 | "log": "日誌",
3 | "resLib": "資源庫",
4 | "materialLib": "素材庫",
5 | "image": "圖片",
6 | "audio": "音訊",
7 | "video": "影片",
8 | "add": "新增",
9 | "use": "使用",
10 | "return": "返回",
11 | "returnDesc": "此操作將不會儲存您目前的模板資料,是否繼續?",
12 | "continue": "繼續",
13 | "delete": "刪除",
14 | "edit": "編輯",
15 | "save": "儲存",
16 | "cancel": "取消",
17 | "confirm": "確認",
18 | "close": "關閉",
19 | "search": "搜尋",
20 | "clear": "清空",
21 | "upload": "上傳",
22 | "download": "下載",
23 | "input": "輸入",
24 | "select": "選擇",
25 | "play": "播放",
26 | "pause": "暫停",
27 | "noData": "暫無資料",
28 | "pleaseSelect": "請選擇",
29 | "enterNameToStartQuery": "輸入名稱開始查詢",
30 | "all": "全部",
31 | "name": "名稱",
32 | "type": "類型",
33 | "size": "大小",
34 | "time": "時間",
35 | "deleteConfirm": "確認刪除?",
36 | "deleteSuccess": "刪除成功",
37 | "deleteFailTip": "刪除失敗,請重試",
38 | "editSuccess": "編輯成功",
39 | "editFailTip": "編輯失敗,請重試",
40 | "uploadSuccess": "上傳成功",
41 | "uploadFailTip": "上傳失敗,請重試",
42 | "downloadSuccess": "下載成功",
43 | "downloadFailTip": "下載失敗,請重試",
44 | "copyTemplate": "複製模板",
45 | "editTemplate": "編輯模板",
46 | "deleteTemplate": "刪除模板",
47 | "saveAsCustomizeTemplate": "儲存為自定義模板",
48 | "preview": "預覽",
49 | "synthesis": "合成",
50 | "chartStyle": "圖表風格",
51 | "synthesisConfig": "合成配置",
52 | "editData": "編輯資料",
53 | "gradientColor": "漸層色",
54 | "themeColor": "主題顏色",
55 | "singleColor": "單一色",
56 | "audioConfig": "音訊配置",
57 | "fullAudio": "完整音訊",
58 | "pleaseSelectAudio": "請選擇音訊",
59 | "audioVolume": "音量大小",
60 | "replace": "替換",
61 | "videoConfig": "影片配置",
62 | "videoFps": "碼率(幀)",
63 | "videoDuration": "時間(秒)",
64 | "videoClarity": "清晰度",
65 | "renewedOnTime": "即時更新",
66 | "loadLocalData": "載入本地資料",
67 | "templateName": "模板名稱",
68 | "defaultTemplate": "默認",
69 | "customizeTemplate": "自定義",
70 | "templateDes": "模板介紹",
71 | "categories": "分類",
72 | "pleaseInput": "請輸入",
73 | "addSubCategories": "新增子分類",
74 | "editCategories": "編輯當前分類",
75 | "deleteCategories": "刪除當前分類",
76 | "dropUpload": "點擊或將文件拖拽至此上傳",
77 | "backgroundConfig": "背景配置",
78 | "themeConfig": "主題色配置",
79 | "display": "顯示",
80 | "textContent": "文字內容",
81 | "fontSize": "字體大小",
82 | "fontColor": "字體顏色",
83 | "textAlign": "對齊方式",
84 | "textPosition": "位置",
85 | "addCategories": "新增分類",
86 | "deleteCategoriesError": "刪除當前分類失敗,可能存在子分類",
87 | "tip": "提示",
88 | "textAlignLeft": "靠左對齊",
89 | "textAlignCenter": "置中對齊",
90 | "textAlignRight": "靠右對齊",
91 | "addSuccess": "新增成功",
92 | "file": "文件",
93 | "textConfig": "文字設定",
94 | "addCategoriesMaxLength": "分類名稱不可超過16個字符",
95 | "systemMessage": "系統訊息",
96 | "serviceReConnect": "合成服務已重新連接",
97 | "serviceConnectClose": "合成服務連接丟失,正在嘗試重新連接...",
98 | "unknown": "未知",
99 | "synthesisSuccess": "合成完成",
100 | "synthesisRead": "合成就緒",
101 | "taskPending": "正在合成中...",
102 | "synthesisError": "合成失敗",
103 | "createTaskSuccess": "創建合成任務成功",
104 | "createTask": "創建合成任務",
105 | "taskName": "任務名稱",
106 | "saveAsCustomTemplate": "是否保存為自定義模板",
107 | "customTemplateName": "自定義模板名稱",
108 | "customTemplateDesc": "自定義模板介紹",
109 | "about": "關於",
110 | "openSourceComponentLicense": "開源元件許可",
111 | "insertRowAbove": "在上方插入行",
112 | "insertRowBelow": "在下方插入行",
113 | "insertRowToLeft": "在左側插入行",
114 | "insertRowToRight": "在右側插入行",
115 | "deleteAnEntireLine": "刪除整行",
116 | "deleteAnEntireColumn": "刪除整列",
117 | "copy": "複製",
118 | "cut": "剪下",
119 | "paste": "貼上",
120 | "undo": "撤銷",
121 | "liveServerNotSupported": "演示伺服器不支持此操作",
122 | "serverError": "伺服器錯誤",
123 | "sysNotify": "系統通知",
124 | "netError": "網路異常,無法與伺服器取得聯繫,請稍後再試。",
125 | "socketError": "與伺服器斷開連接,正在重試...",
126 | "socketReConnect": "已重新與伺服器連接",
127 | "createSuccess": "創建成功",
128 | "getStarted": "立即開始",
129 | "serverInfo": "伺服器資訊",
130 | "memory": "記憶體",
131 | "appUseMemory": "程序記憶體用量",
132 | "version": "版本",
133 | "heapTotal": "堆總計",
134 | "heapUsed": "堆已使用",
135 | "arrayBuffers": "緩衝區大小",
136 | "status": "狀態",
137 | "synthesisServicesConnectStatus": "合成伺服器連接狀態",
138 | "connected": "已連接",
139 | "notConnected": "未連接"
140 | }
141 |
--------------------------------------------------------------------------------
/web_app/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import {initReactI18next} from "react-i18next";
3 | import zhCn from './config/zhCn.json';
4 | import zhTw from './config/zhTw.json';
5 | import koKr from './config/koKr.json';
6 | import jpJp from './config/jpJp.json';
7 | import ekUk from './config/enUk.json';
8 | import ruRU from './config/ruRU.json';
9 | import MyStorage from "../lib/Storage";
10 |
11 | const resources = {
12 | 'zh-CN': {
13 | translation: zhCn
14 | },
15 | 'zh-TW': {
16 | translation: zhTw
17 | },
18 | 'ko': {
19 | translation: koKr
20 | },
21 | 'jp': {
22 | translation: jpJp
23 | },
24 | 'en': {
25 | translation: ekUk
26 | },
27 | 'ru-RU': {
28 | translation: ruRU
29 | }
30 | };
31 |
32 | i18n.use(initReactI18next)
33 | .init({
34 | resources: resources,
35 | lng: MyStorage.get('lang') ?? 'zh-CN',
36 | interpolation: {
37 | escapeValue: false
38 | }
39 | });
40 |
41 | export default i18n;
42 |
--------------------------------------------------------------------------------
/web_app/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, {lazy, StrictMode, Suspense} from "react";
2 | import {createRoot, Root} from 'react-dom/client';
3 | import reportWebVitals from './reportWebVitals';
4 | import {Provider} from "react-redux";
5 | import {BrowserRouter, Route, Routes} from "react-router-dom";
6 | import Store from "./lib/Store/";
7 | import "./style/Global.css";
8 | import "./style/index.scss";
9 | import "./style/animate.min.css";
10 | import "./style/hi.scss";
11 | import "./pages/style/AppHome.scss";
12 | import "./pages/style/AppHome-Dack.scss";
13 | import "./pages/style/AppHome-Light.scss";
14 | import "./pages/style/AppDesign.scss";
15 | import "./pages/style/AppDesign-Dack.scss";
16 | import "./pages/style/AppDesign-Light.scss";
17 | import "./pages/style/Welcome.scss";
18 | import "./i18n";
19 | import "./lib/Socket";
20 |
21 | ((): void => {
22 | const AppHome: React.LazyExoticComponent<() => React.JSX.Element> = lazy(() => import("./pages/AppHome"));
23 | const AppDesign: React.LazyExoticComponent<() => React.JSX.Element> = lazy(() => import("./pages/AppDesign"));
24 | const Welcome: React.LazyExoticComponent<() => React.JSX.Element> = lazy(() => import("./pages/Welcome"));
25 | const RootDom: HTMLDivElement = document.getElementById('__lmo_app') as HTMLDivElement;
26 | const __RootApp: Root = createRoot(RootDom);
27 |
28 | document.body.classList.add('dark-mode');
29 |
30 | __RootApp.render(
31 |
32 |
33 |
34 |
35 | }/>
36 | }/>
37 | }/>
38 |
39 |
40 |
41 |
42 | );
43 | })();
44 |
45 | reportWebVitals();
46 |
47 | ((_GLOBAL: Window): void => {
48 | 'use strict';
49 |
50 | const getId = (): string => (new Date().getTime() / Math.random()).toFixed(0).toString() + Math.random().toString(36).substring(2, 5).toString();
51 |
52 | _GLOBAL.__LMO_APP_CONFIG = {
53 | __PLAYER_EL_ID: `__lmo_dv_app_player_${getId()}`,
54 | __PREVIEWER_EL_ID: `__lmo_dv_app_preview_${getId()}`
55 | } as const;
56 |
57 | Object.freeze(_GLOBAL.__LMO_APP_CONFIG);
58 |
59 | ((): void => {
60 | const audio: HTMLAudioElement = 'Audio' in _GLOBAL ? new Audio() : document.createElement('audio');
61 |
62 | audio.id = _GLOBAL.__LMO_APP_CONFIG.__PLAYER_EL_ID;
63 | document.body.append(audio);
64 | })();
65 |
66 | })(this ?? window);
67 |
--------------------------------------------------------------------------------
/web_app/src/lib/AudioPlayer/index.ts:
--------------------------------------------------------------------------------
1 | export interface AudioPlayerInterface {
2 | isDestroy: boolean;
3 | canplay: boolean;
4 | load: () => void;
5 | setSrc: (src: string) => Promise;
6 | play: (s?: boolean) => void;
7 | setVolume: (v: number) => void;
8 | destroy: () => void;
9 | continue: (src: string) => Promise;
10 | pause: (src: string) => Promise;
11 | getRef: () => HTMLAudioElement;
12 | addEventListener: (event: T, listener: (...args: Array) => any) => void;
13 | removeEventListener: (event: T, listener: (...args: Array) => any) => void;
14 | }
15 |
16 | class AudioPlayer implements AudioPlayerInterface {
17 | public isDestroy: boolean;
18 | public canplay: boolean;
19 | private audioObject: HTMLAudioElement | null;
20 | private url: string;
21 |
22 | constructor(url: string) {
23 | this.url = url;
24 | this.audioObject = null;
25 | this.isDestroy = false;
26 | this.canplay = false;
27 | this.init();
28 | }
29 |
30 | public load(): void {
31 | this.audioObject?.paused && this.audioObject.pause();
32 | this.audioObject?.load();
33 | }
34 |
35 | public async setSrc(url: string): Promise {
36 | this.url = url;
37 | await this.pause();
38 | this.audioObject && (this.audioObject.src = this.url);
39 | this.load();
40 | }
41 |
42 | public play(s: boolean = false): void {
43 | this.pause().then(async (): Promise => {
44 | if (s) {
45 | this.audioObject && (this.audioObject.currentTime = 0);
46 | setTimeout(async (): Promise => {
47 | await this.audioObject?.play();
48 | });
49 | } else
50 | await this.continue();
51 | });
52 | }
53 |
54 | public setVolume(v: number): void {
55 | this.audioObject && (this.audioObject.volume = v);
56 | }
57 |
58 | public destroy(): void {
59 | if (!this.audioObject?.paused)
60 | this.pause();
61 | this.audioObject = null;
62 | this.isDestroy = true;
63 | }
64 |
65 | public async continue(): Promise {
66 | await this.audioObject?.play();
67 | }
68 |
69 | public getRef(): HTMLAudioElement {
70 | return this.audioObject as HTMLAudioElement;
71 | }
72 |
73 | public addEventListener(event: T, listener: (...args: Array) => any): void {
74 | if (event)
75 | this.audioObject?.addEventListener(event, listener);
76 | }
77 |
78 | // 移除事件侦听器
79 | public removeEventListener(event: T, listener: (...args: Array) => any): void {
80 | if (event)
81 | this.audioObject?.removeEventListener(event, listener);
82 | }
83 |
84 | public pause(): Promise {
85 | return new Promise((resolve): void => {
86 | setTimeout((): void => {
87 | if (!this.audioObject?.paused) {
88 | this.audioObject?.pause();
89 | }
90 | resolve();
91 | });
92 | });
93 | }
94 |
95 | private init(): void {
96 | if (this.audioObject === null && !this.isDestroy) {
97 | if (!('Audio' in window)) {
98 | throw '(AudioPlayer) Error: Your browser does not support audio playback.';
99 | }
100 | this.audioObject = new Audio(this.url);
101 | this.audioObject.addEventListener('ended', (): void => {
102 | this.audioObject?.pause();
103 | });
104 | this.audioObject.addEventListener('canplaythrough', (): void => {
105 | this.canplay = true;
106 | });
107 | }
108 | }
109 | }
110 |
111 | export default AudioPlayer;
112 |
--------------------------------------------------------------------------------
/web_app/src/lib/Notification.ts:
--------------------------------------------------------------------------------
1 | import {notification} from "@hi-ui/hiui";
2 | import message from "@hi-ui/message";
3 |
4 | class Notification {
5 | public static openNotification(title: string = '通知', content: string, type: 'info' | 'success' | 'error' | 'warning' = 'error'): void {
6 | notification.closeAll();
7 | notification.open({
8 | title: title,
9 | type: type,
10 | content: content
11 | });
12 | }
13 |
14 | public static message(title: string, type: 'info' | 'success' | 'error' | 'warning'): void {
15 | message.closeAll();
16 | message.open({
17 | title: title,
18 | type: type
19 | });
20 | }
21 | }
22 |
23 | export default Notification;
24 |
--------------------------------------------------------------------------------
/web_app/src/lib/Nprogress/index.ts:
--------------------------------------------------------------------------------
1 | import "./style.scss";
2 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3 | // @ts-expect-error
4 | import * as N from "nprogress";
5 |
6 | namespace Nprogress {
7 | export const start = (d: number = 0.1): void => {
8 | N['set'](d);
9 | setTimeout(async (): Promise => {
10 | await N['start']();
11 | });
12 | };
13 |
14 | export const done = (): void => {
15 | setTimeout(async (): Promise => {
16 | await N['done']();
17 | });
18 | };
19 | }
20 |
21 | export default Nprogress;
22 |
23 |
24 |
--------------------------------------------------------------------------------
/web_app/src/lib/Nprogress/style.scss:
--------------------------------------------------------------------------------
1 | #nprogress .bar {
2 | height: 4px !important;
3 | background-color: #229EED !important;
4 | }
5 |
--------------------------------------------------------------------------------
/web_app/src/lib/PostMessage/index.ts:
--------------------------------------------------------------------------------
1 | import {TDesignAppMessageType} from "../../types/TemplateMessage";
2 |
3 | namespace PostMessage {
4 | export const send = (message: {
5 | type: TDesignAppMessageType,
6 | message: object
7 | }): void => {
8 | const _: HTMLIFrameElement = document.querySelector('iframe') as HTMLIFrameElement;
9 |
10 | _.contentWindow?.postMessage(message, location.origin);
11 | };
12 | }
13 |
14 | export default PostMessage;
15 |
--------------------------------------------------------------------------------
/web_app/src/lib/Socket/index.ts:
--------------------------------------------------------------------------------
1 | import Notification from "../Notification";
2 | import i18n from "../../i18n";
3 |
4 | class Socket {
5 | private ws: WebSocket | undefined;
6 | private connectNum: number;
7 | private maxConnectNum: number = 5;
8 |
9 | constructor() {
10 | this.connectNum = -1;
11 | this.init();
12 | }
13 |
14 | public sendMessage(message: string): void {
15 | this.ws && this.ws.send(message);
16 | }
17 |
18 | public addMessageEventListener(listener: (e: MessageEvent) => void): void {
19 | this.ws && this.ws.addEventListener('message', listener);
20 | }
21 |
22 | public removeMessageEventListener(listener: (e: MessageEvent) => void): void {
23 | this.ws && this.ws.removeEventListener('message', listener);
24 | }
25 |
26 | private init(): void {
27 | this.connectNum = this.connectNum + 1;
28 | if (this.connectNum === this.maxConnectNum)
29 | return Notification.openNotification(i18n.t('sysNotify'), i18n.t('netError'), 'error');
30 | this.ws && this.ws.close();
31 | this.ws = new WebSocket(`${location.href.includes('https') ? 'wss' : 'ws'}://${location.host}/connect`);
32 |
33 | this.ws.onopen = (): void => {
34 | if (this.connectNum > 0)
35 | Notification.openNotification(i18n.t('sysNotify'), i18n.t('socketReConnect'), 'success');
36 | this.sendMessage('ping');
37 |
38 | setInterval((): void => {
39 | this.heartbeat();
40 | }, 10000);
41 | };
42 | this.ws.onclose = (): void => {
43 | Notification.openNotification(i18n.t('sysNotify'), i18n.t('socketError'), 'error');
44 | setTimeout((): void => {
45 | this.init();
46 | }, 5000);
47 | };
48 | this.ws.onmessage = (e: MessageEvent): void => {
49 | this.onMessage(e.data);
50 | };
51 | }
52 |
53 | private heartbeat(): void {
54 | this.ws && this.ws.send('ping');
55 | }
56 |
57 | private onMessage(message: string): void {
58 | try {
59 | if (message === 'pong') return;
60 |
61 | const data = JSON.parse(message);
62 |
63 | if (data.type === 'TASK_END')
64 | Notification.openNotification(i18n.t('systemMessage'), `[${data.message.name ?? i18n.t('unknown')}] ${i18n.t('synthesisSuccess')}`, 'success');
65 | if (data.type === 'TASK_READY')
66 | Notification.openNotification(i18n.t('systemMessage'), `[${data.message.name ?? i18n.t('unknown')}] ${i18n.t('synthesisRead')}`, 'info');
67 | if (data.type === 'SERVICE_RE_CONNECT_SUCCESS')
68 | Notification.openNotification(i18n.t('systemMessage'), i18n.t('serviceReConnect'), 'success');
69 | if (data.type === 'SERVICE_CONNECT_CLOSE')
70 | Notification.openNotification(i18n.t('systemMessage'), i18n.t('serviceConnectClose'), 'warning');
71 |
72 | } catch (e) {
73 | console.log('error', e);
74 | }
75 | }
76 | }
77 |
78 | export default new Socket();
79 |
--------------------------------------------------------------------------------
/web_app/src/lib/Storage/index.ts:
--------------------------------------------------------------------------------
1 | import AppConfig from "../../config/AppConfig";
2 | import Utils from "../../utils";
3 |
4 | namespace MyStorage {
5 | const storage: Storage = AppConfig.storageOptions.storage === 'local' ? localStorage : sessionStorage;
6 | const namespace: string = AppConfig.storageOptions.namespace;
7 |
8 | export const set = (key: string, value: any): void => {
9 | return storage.setItem(`${namespace}${key}`, Utils.toString(value));
10 | };
11 |
12 | export const get = (key: string): string | null => {
13 | return storage.getItem(`${namespace}${key}`);
14 | };
15 |
16 | export const remove = (key: string): void => {
17 | return storage.removeItem(key);
18 | };
19 |
20 | export const clear = (): void => {
21 | return storage.clear();
22 | };
23 | }
24 |
25 | export default MyStorage;
26 |
--------------------------------------------------------------------------------
/web_app/src/lib/Store/AppStore.ts:
--------------------------------------------------------------------------------
1 | import {createSlice} from '@reduxjs/toolkit';
2 |
3 | const defaultCurrentTemplateConfigData = {
4 | data: [],
5 | config: {
6 | text: {},
7 | theme: {
8 | type: '',
9 | configs: [],
10 | value: []
11 | },
12 | background: {
13 | type: '',
14 | color: '',
15 | image: '',
16 | arrangement: ''
17 | },
18 | video: {
19 | duration: 5000,
20 | fps: 30,
21 | clarity: '1080P'
22 | },
23 | audio: {
24 | path: '',
25 | full: false,
26 | src: '',
27 | volume: 100
28 | },
29 | animation: {
30 | chatAnimationIsControllable: false
31 | }
32 | },
33 | otherConfig: {
34 | label: '',
35 | configs: [],
36 | group: [],
37 | values: {}
38 | }
39 | };
40 |
41 | const AppStore = createSlice({
42 | name: 'app',
43 | initialState: {
44 | currentTemplate: {
45 | cover: "",
46 | createTime: "",
47 | description: "",
48 | id: "",
49 | name: "",
50 | path: "",
51 | type: 0
52 | },
53 | currentTemplateConfig: {
54 | ...defaultCurrentTemplateConfigData
55 | }
56 | },
57 | reducers: {
58 | // 初始化模板配置文件
59 | initCurrentTemplateConfigData(state): void {
60 | state.currentTemplateConfig = {
61 | ...defaultCurrentTemplateConfigData
62 | };
63 | },
64 | // 设置当前模板
65 | setCurrentTemplate(state, {payload}): void {
66 | state.currentTemplate = payload;
67 | },
68 | // 设置模板配置文件
69 | setCurrentTemplateConfig(state, {payload}): void {
70 | state.currentTemplateConfig = payload;
71 | },
72 | // 设置模板背景
73 | setCurrentTemplateBackground(state, {payload}): void {
74 | state.currentTemplateConfig.config.background = payload;
75 | },
76 | // 设置模板其他配置
77 | setCurrentTemplateOtherConfigValues(state, {payload}): void {
78 | state.currentTemplateConfig.otherConfig.values = payload;
79 | },
80 | // 设置模板主题配置
81 | setCurrentTemplateThemeConfig(state, {payload}): void {
82 | state.currentTemplateConfig.config.theme = payload;
83 | },
84 | // 设置模板音频配置
85 | setCurrentTemplateAudioConfig(state, {payload}): void {
86 | state.currentTemplateConfig.config.audio = payload;
87 | },
88 | // 设置模板视频配置
89 | setCurrentTemplateVideoConfig(state, {payload}): void {
90 | state.currentTemplateConfig.config.video = payload;
91 | },
92 | setCurrentTemplateData(state, {payload}): void {
93 | state.currentTemplateConfig.data = payload;
94 | }
95 | }
96 | });
97 |
98 | export const {
99 | setCurrentTemplate,
100 | setCurrentTemplateConfig,
101 | setCurrentTemplateBackground,
102 | setCurrentTemplateOtherConfigValues,
103 | setCurrentTemplateThemeConfig,
104 | setCurrentTemplateAudioConfig,
105 | setCurrentTemplateVideoConfig,
106 | initCurrentTemplateConfigData,
107 | setCurrentTemplateData
108 | } = AppStore.actions;
109 |
110 | export default AppStore.reducer;
111 |
--------------------------------------------------------------------------------
/web_app/src/lib/Store/index.ts:
--------------------------------------------------------------------------------
1 | import {configureStore} from '@reduxjs/toolkit';
2 | import AppStore from "./AppStore";
3 |
4 | const store = configureStore({
5 | reducer: {
6 | app: AppStore
7 | }
8 | });
9 |
10 | export default store;
11 | export type RootState = ReturnType;
12 | export type AppDispatch = typeof store.dispatch;
13 |
--------------------------------------------------------------------------------
/web_app/src/lib/Store/interface/AppState.ts:
--------------------------------------------------------------------------------
1 | interface AppState {
2 | // 当前模板配置信息
3 | currentConfig: {
4 | csvData: Array | Array;
5 | config: {},
6 | duration: number;
7 | },
8 | // 当前模板音频配置信息
9 | templateCurrentAudioConfig: {
10 | name: string;
11 | volume: number,
12 | src: string,
13 | complete: boolean,
14 | playState: boolean
15 | },
16 | // 当前模板默认数据
17 | currentTemplateDefaultData: {},
18 | // 当前模板视频配置信息(合成时)
19 | currentTemplateVideoConfig: {},
20 | // 当前模板信息
21 | currentTemplate: null
22 | }
23 |
24 | export interface IAppStore {
25 | currentTemplate: {
26 | cover: string;
27 | createTime: string;
28 | description: string;
29 | id: string;
30 | name: string;
31 | path: string;
32 | type: number;
33 | };
34 | currentTemplateConfig: {
35 | data: any;
36 | config: {
37 | text: {
38 | [key: string]: {
39 | color: string;
40 | value: string;
41 | display: boolean;
42 | fontSize: number;
43 | align: string;
44 | width: number;
45 | height: number;
46 | x: number;
47 | y: number;
48 | }
49 | };
50 | theme: {
51 | type: string;
52 | configs: Array;
53 | values: Array;
54 | },
55 | background: {
56 | type: string;
57 | color: string;
58 | image: string;
59 | arrangement: string;
60 | },
61 | video: {
62 | duration: number;
63 | fps: number;
64 | clarity: string;
65 | },
66 | audio: {
67 | path: string;
68 | full: boolean;
69 | volume: number;
70 | }
71 | },
72 | otherConfig: {
73 | label: string;
74 | configs: Array<{
75 | label: string;
76 | key: string;
77 | type: string;
78 | value: any;
79 | }>;
80 | values: {
81 | [key: string]: any;
82 | }
83 | }
84 | }
85 | }
86 |
87 | export default AppState;
88 |
--------------------------------------------------------------------------------
/web_app/src/pages/AppDesign.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef} from "react";
2 | import Grid from "@hi-ui/grid";
3 | import TemplatePreview from "../components/Preview";
4 | import ProgressBar from "../components/ProgressBar";
5 | import DesignConfigs from "../components/DesignConfigs";
6 | import Header from "../components/Header";
7 | import {Dispatch} from "@reduxjs/toolkit";
8 | import {useDispatch, useSelector} from "react-redux";
9 | import {RootState} from "../lib/Store";
10 | import Storage from "../lib/Storage";
11 | import {setCurrentTemplate} from "../lib/Store/AppStore";
12 | import Task, {ICreateTaskRef} from "../components/Task";
13 | import AudioPreview from "../components/AudioPreview";
14 | import {NavigateFunction, useNavigate} from "react-router-dom";
15 |
16 | const AppDesign = (): React.JSX.Element => {
17 | const dispatch: Dispatch = useDispatch();
18 | const currentTemplate = useSelector((state: RootState) => state.app.currentTemplate);
19 | const taskRef: React.RefObject = useRef(null);
20 | const navigate: NavigateFunction = useNavigate();
21 |
22 | useEffect((): void => {
23 | if (currentTemplate.id === '') {
24 | try {
25 | const currentTemplate = JSON.parse(Storage.get('current_template') ?? '');
26 |
27 | if (Object.keys(currentTemplate).length > 0)
28 | dispatch(setCurrentTemplate(currentTemplate));
29 | else
30 | navigate('/');
31 | } catch (e) {
32 | navigate('/');
33 | }
34 | }
35 | }, []);
36 | return (
37 |
38 |
{
40 | taskRef.current?.open('synthesis');
41 | }}
42 | onSave={(): void => {
43 | taskRef.current?.open('savaAsTemplate');
44 | }}
45 | />
46 |
47 |
48 |
51 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default AppDesign;
71 |
--------------------------------------------------------------------------------
/web_app/src/pages/AppHome.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from "react";
2 | import TemplateList from "../components/TemplateList";
3 | import Header from "../components/Header";
4 | import Storage from "../lib/Storage";
5 | import {NavigateFunction, useNavigate} from "react-router-dom";
6 | import ServerInfo from "../components/ServerInfo";
7 |
8 | const AppHome = (): React.JSX.Element => {
9 | const navigate: NavigateFunction = useNavigate();
10 |
11 | useEffect((): void => {
12 | if (!location.pathname.includes('welcome') && Storage.get('open_welcome_page') === null)
13 | navigate('/welcome');
14 | }, []);
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default AppHome;
28 |
--------------------------------------------------------------------------------
/web_app/src/pages/style/AppDesign-Dack.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/theme";
2 |
3 | .dark-mode {
4 | .data-visualization-design {
5 | background: $__primary-sub-bg-color-dark;
6 |
7 | .data-visualization-design-header {
8 | background-color: $__primary-bg-color-dark;
9 |
10 | .data-visualization-design-header-black {
11 | color: $color-white;
12 | }
13 | }
14 |
15 | .template-preview {
16 | background: $__primary-bg-color-dark;
17 |
18 | iframe {
19 | background: $__primary-bg-color-dark;
20 | }
21 | }
22 |
23 | .progress-bar {
24 | background: $__primary-bg-color-dark;
25 | }
26 |
27 | .design-configs {
28 | background: $__primary-bg-color-dark;
29 |
30 | .select-bg-image {
31 | background-color: #354459;
32 | }
33 |
34 | .design-configs-top-options {
35 | background: #2F3C4E;
36 | }
37 |
38 | .design-configs-container {
39 | .text-config {
40 | .text-config-title {
41 | color: $color-white;
42 | }
43 | }
44 |
45 | .text-config-item {
46 | margin: 1.375em 0;
47 |
48 | .text-config-item-label {
49 | color: $color-white;
50 | }
51 | }
52 |
53 | .color-config {
54 | .color-config-item-label {
55 | color: $color-white;
56 | }
57 | }
58 | }
59 | }
60 |
61 | .config-line {
62 | background: #2F3E51;
63 | }
64 | }
65 |
66 | .c-gradient-color-picker {
67 | background: $__form-component-bg-color-dark;
68 | }
69 |
70 | .c-reset-button {
71 | background: $__form-component-bg-color-dark;
72 | }
73 |
74 | .hi-v4-card {
75 | background: #334358;
76 | }
77 |
78 | .hi-v4-card--bordered {
79 | border: 1px solid #ffffff00;
80 | }
81 |
82 | .category-tree-box {
83 | background: #2F3C4E !important;
84 | }
85 |
86 | .c-audio-item-name {
87 | color: $color-white;
88 | }
89 |
90 | .c-resources-item {
91 | background: #334358;
92 | border: 1px solid #eeeff100;
93 |
94 | .c-resources-item-info-title {
95 | color: $color-white !important;
96 | }
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/web_app/src/pages/style/AppDesign-Light.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/theme";
2 |
3 | .light-mode {
4 | .data-visualization-design {
5 | background: $__primary-high-bg-color-light;
6 |
7 | .data-visualization-design-header {
8 | background-color: $__primary-sub-bg-color-light;
9 | box-shadow: none;
10 |
11 | .data-visualization-design-header-black {
12 | color: $__primary-input-color-light;
13 |
14 | svg {
15 | background-color: $__primary-input-color-light;
16 | border-radius: 50%;
17 | }
18 | }
19 | }
20 |
21 | .template-preview {
22 | background: $__primary-sub-bg-color-light;
23 | box-shadow: none;
24 |
25 | iframe {
26 | background: $__primary-sub-bg-color-light;
27 | }
28 | }
29 |
30 | .progress-bar {
31 | background: $__primary-sub-bg-color-light;
32 | box-shadow: none;
33 | }
34 |
35 | .design-configs {
36 | background: $__primary-sub-bg-color-light;
37 |
38 | .select-bg-image {
39 | background-color: #EDF2F5;
40 | }
41 |
42 | .design-configs-top-options {
43 | background: $__primary-bg-color-light;
44 | box-shadow: none;
45 | }
46 |
47 | .design-configs-container {
48 | .text-config {
49 | .text-config-title {
50 | color: $__primary-input-color-light;
51 | }
52 | }
53 |
54 | .color-config {
55 | .color-config-item-label {
56 | color: $__primary-input-color-light;
57 | }
58 | }
59 |
60 | .text-config-item {
61 | margin-top: 1.375em;
62 |
63 | .text-config-item-label {
64 | color: $__primary-input-color-light;
65 | }
66 | }
67 |
68 | }
69 | }
70 |
71 | .config-line {
72 | background: #EAEAEA;
73 | }
74 | }
75 |
76 | .c-gradient-color-picker {
77 | background: $__primary-input-bg-color-light;
78 | }
79 |
80 | .c-reset-button {
81 | background: $__primary-input-bg-color-light;
82 |
83 | svg {
84 | path {
85 | fill: $__primary-input-color-light;
86 | }
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/web_app/src/pages/style/AppHome-Dack.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/theme";
2 |
3 | .dark-mode {
4 | .data-visualization {
5 | background-color: $__primary-sub-bg-color-dark;
6 |
7 | .data-visualization-header {
8 | background-color: $__primary-bg-color-dark;
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/web_app/src/pages/style/AppHome-Light.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/theme";
2 |
3 | .light-mode {
4 | .data-visualization {
5 | background-color: $__primary-bg-color-light;
6 | }
7 |
8 | .data-visualization-header {
9 | background-color: $__primary-sub-bg-color-light;
10 |
11 | .data-visualization-header-app-name {
12 | color: $theme-color;
13 | }
14 | }
15 |
16 | .data-visualization-templates {
17 | .hi-v4-input__text {
18 | color: #3D3D3D !important;
19 | }
20 |
21 | .hi-v4-mock-input {
22 | background-color: #fff !important;
23 | }
24 |
25 | .hi-v4-input__inner {
26 | border: 0 !important;
27 | background: #FFF !important;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/web_app/src/pages/style/AppHome.scss:
--------------------------------------------------------------------------------
1 | @import "../../style/theme";
2 |
3 | .data-visualization {
4 | width: 100%;
5 | height: 100%;
6 | position: absolute;
7 |
8 | .data-visualization-header {
9 | width: 100vw;
10 | height: 4.125rem;
11 | box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25);
12 | overflow: hidden;
13 |
14 | .data-visualization-header-app-name {
15 | font-size: 20px;
16 | color: #ffffff;
17 | font-weight: bold;
18 | line-height: 66px;
19 | margin-left: 2.875rem;
20 | transition: all .2s;
21 |
22 | &:hover {
23 | color: #0688E5;
24 | }
25 | }
26 |
27 | .data-visualization-header-option {
28 | margin-right: 2.875rem;
29 | margin-top: 1rem;
30 | display: flex;
31 | justify-content: end;
32 | }
33 | }
34 |
35 | .data-visualization-templates {
36 | width: 100vw;
37 | height: calc(100vh - 4.125rem);
38 | overflow: scroll;
39 | }
40 |
41 | }
42 |
43 |
44 |
--------------------------------------------------------------------------------
/web_app/src/pages/style/Welcome.scss:
--------------------------------------------------------------------------------
1 | .welcome-page {
2 | width: 100%;
3 | height: 100%;
4 | background-repeat: no-repeat;
5 | background-size: cover;
6 |
7 | &-content {
8 | width: auto;
9 | position: relative;
10 | margin: calc((100vh - 318px) / 2) auto auto;
11 | }
12 |
13 | &-logo {
14 | width: 11.25rem;
15 | height: 11.25rem;
16 | flex-shrink: 0;
17 | }
18 |
19 | h2 {
20 | color: #FFFFFF;
21 | font-size: 2rem;
22 | font-style: normal;
23 | font-weight: 600;
24 | line-height: normal;
25 | }
26 |
27 | .start-button {
28 | padding-left: 3.69rem;
29 | padding-right: calc(3.69rem + .62rem);
30 | height: 3.75rem;
31 | flex-shrink: 0;
32 | border-radius: 1.25rem;
33 | background: #FFF;
34 |
35 | p {
36 | text-align: center;
37 | line-height: 3.75rem;
38 | color: #0688E5;
39 | font-size: 1.5rem;
40 | font-style: normal;
41 | font-weight: 500;
42 | }
43 |
44 | svg {
45 | top: 1.2rem;
46 | margin-left: .62rem;
47 | }
48 | }
49 |
50 |
51 | &-right-button {
52 | right: 5%;
53 | bottom: 10%;
54 |
55 | svg {
56 | font-size: 2rem !important;
57 | color: #FFFFFF;
58 | transition: transform 1s;
59 |
60 | &:hover {
61 | animation: rotate 1s forwards !important;
62 | }
63 | }
64 | }
65 |
66 | &-footer {
67 | bottom: 1%;
68 | width: 100%;
69 |
70 | p {
71 | text-align: center;
72 | font-size: .8rem;
73 | color: #ddd;
74 |
75 | a {
76 | color: #fff;
77 | }
78 | }
79 | }
80 | }
81 |
82 | @keyframes rotate {
83 | from {
84 | transform: rotate(0deg);
85 | }
86 | to {
87 | transform: rotate(360deg);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/web_app/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/web_app/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import {ReportHandler} from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/web_app/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/web_app/src/style/Global.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | html, body, div, span, applet, object, iframe,
3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
4 | a, abbr, acronym, address, big, cite, code,
5 | del, dfn, em, font, img, ins, kbd, q, s, samp,
6 | small, strike, strong, sub, sup, tt, var,
7 | b, u, i, center,
8 | dl, dt, dd, ol, ul, li,
9 | fieldset, form, label, legend,
10 | table, caption, tbody, tfoot, thead, tr, th, td {
11 | margin: 0;
12 | padding: 0;
13 | border: 0;
14 | outline: 0;
15 | font-size: 100%;
16 | }
17 |
18 | table {
19 | border-collapse: collapse;
20 | border-spacing: 0;
21 | }
22 |
23 | fieldset, img {
24 | border: 0;
25 | }
26 |
27 | address, caption, cite, code, dfn, em, strong, th, var {
28 | font-style: normal;
29 | font-weight: normal;
30 | }
31 |
32 | strong {
33 | font-weight: bold;
34 | }
35 |
36 | ol, ul {
37 | list-style: none;
38 | }
39 |
40 | caption, th {
41 | text-align: left;
42 | }
43 |
44 | h1, h2, h3, h4, h5, h6 {
45 | font-size: 200%;
46 | font-weight: normal;
47 | }
48 |
49 | :focus {
50 | outline: 0;
51 | }
52 |
53 | a {
54 | text-decoration: none;
55 | }
56 |
57 | a:hover {
58 | text-decoration: none;
59 | }
60 |
61 | a:hover img {
62 | border: none;
63 | }
64 |
65 | legend {
66 | color: #000;
67 | }
68 |
69 | fieldset img {
70 | border: 0;
71 | }
72 |
73 | .clearfix {
74 | display: inline-block;
75 | }
76 |
77 | .clearfix:after {
78 | content: ".";
79 | display: block;
80 | height: 0;
81 | clear: both;
82 | visibility: hidden;
83 | }
84 |
85 | * html .clearfix {
86 | height: 1%;
87 | }
88 |
89 | .clearfix {
90 | display: block;
91 | }
92 |
93 | ::-webkit-scrollbar {
94 | width: 8px;
95 | background-color: rgba(255, 255, 255, 0);
96 | }
97 |
98 | ::-webkit-scrollbar-thumb {
99 | background-color: rgba(255, 255, 255, 0);
100 | border-radius: 10px;
101 | }
102 |
--------------------------------------------------------------------------------
/web_app/src/style/fonts/JetbrainsMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayuanlmo/lmo-data-visualization/119a911ca9ae6e7af0724a6cc2b7939a2d653a26/web_app/src/style/fonts/JetbrainsMono.ttf
--------------------------------------------------------------------------------
/web_app/src/style/fonts/element-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayuanlmo/lmo-data-visualization/119a911ca9ae6e7af0724a6cc2b7939a2d653a26/web_app/src/style/fonts/element-icons.ttf
--------------------------------------------------------------------------------
/web_app/src/style/fonts/element-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayuanlmo/lmo-data-visualization/119a911ca9ae6e7af0724a6cc2b7939a2d653a26/web_app/src/style/fonts/element-icons.woff
--------------------------------------------------------------------------------
/web_app/src/style/index.scss:
--------------------------------------------------------------------------------
1 | @import "theme";
2 |
3 | @font-face {
4 | font-family: 'JetBrains Mono';
5 | src: url("./fonts/JetbrainsMono.ttf");
6 | }
7 |
8 | .app_theme_color {
9 | color: $theme-color;
10 | }
11 |
12 | .app_warning_color {
13 | color: $warning-color;
14 | }
15 |
16 | .app_danger_color {
17 | color: $danger-color;
18 | }
19 |
20 | .app_danger_color_hover {
21 | &:hover {
22 | color: $danger-color;
23 | }
24 | }
25 |
26 | .app_warning_color_hover {
27 | &:hover {
28 | color: $warning-color;
29 | }
30 | }
31 |
32 | .app_hover_theme_color {
33 | &:hover {
34 | color: $theme-color;
35 | }
36 | }
37 |
38 | .app_hide {
39 | display: none !important;
40 | }
41 |
42 | .app_theme_color_border {
43 | border-color: $theme-color;
44 | }
45 |
46 | .app_theme_color_background {
47 | background-color: $theme-color;
48 | }
49 |
50 | .app_cursor_pointer {
51 | cursor: pointer;
52 | }
53 |
54 | .app_cursor_pointer_hover {
55 | cursor: pointer;
56 | }
57 |
58 | .app_none_user_select {
59 | user-select: none;
60 | }
61 |
62 | .app_flex_box {
63 | display: flex;
64 | }
65 |
66 | .app_block_box {
67 | display: block;
68 | }
69 |
70 | .app_position_relative {
71 | position: relative;
72 | }
73 |
74 | .app_position_absolute {
75 | position: absolute;
76 | }
77 |
78 | .app_color_white {
79 | color: $color-white;
80 | }
81 |
82 | .app_color_white_background {
83 | background-color: $color-white;
84 | }
85 |
86 | .c-g-switch-theme {
87 | width: 3.125rem;
88 | height: 1.5rem;
89 | border-radius: 0.75rem;
90 | position: relative;
91 | overflow: hidden;
92 | margin-top: 0.25rem;
93 | margin-right: 1.25rem;
94 |
95 | .c-g-switch-theme-content {
96 | content: '';
97 | display: block;
98 | width: 1.25rem;
99 | height: 1.25rem;
100 | border-radius: 50%;
101 | margin-top: 0.125rem;
102 | transition: all .2s;
103 | margin-left: 0.125rem;
104 |
105 | span {
106 | display: block;
107 |
108 | svg {
109 | top: .25rem;
110 | left: .25rem;
111 | }
112 | }
113 | }
114 | }
115 |
116 | .c-g-switch-theme-selected {
117 | .c-g-switch-theme-content {
118 | margin-left: calc(3.125rem - (1.25rem + 0.125rem));
119 | }
120 | }
121 |
122 | .light-mode {
123 | .c-g-switch-theme {
124 | background-color: #334358;
125 |
126 | .c-g-switch-theme-content {
127 | background-color: #091628;
128 | }
129 | }
130 | }
131 |
132 | .dark-mode {
133 | .c-g-switch-theme {
134 | background-color: #EDF2F5;
135 |
136 | .c-g-switch-theme-content {
137 | background-color: #FFFFFF;
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/web_app/src/style/theme.scss:
--------------------------------------------------------------------------------
1 | $theme-color: #0688E5;
2 | $warning-color: #e6a23c;
3 | $danger-color: #f56c6c;
4 | $color-white: #FBFBFD;
5 |
6 | // dark
7 | $__primary-bg-color-dark: #232F3F;
8 | $__primary-sub-bg-color-dark: #1A2236;
9 | $__form-component-bg-color-dark: #354459;
10 |
11 |
12 | // light
13 | $__primary-bg-color-light: #F0F0F0;
14 | $__primary-high-bg-color-light: #F8F9FB;
15 | $__primary-sub-bg-color-light: #FFF;
16 | $__primary-input-bg-color-light: #cdd2d5;
17 | $__primary-input-color-light: #3D3D3D;
--------------------------------------------------------------------------------
/web_app/src/svg/lang.ts:
--------------------------------------------------------------------------------
1 | export default ``;
--------------------------------------------------------------------------------
/web_app/src/types/ReactTypes.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export type ReactState = [T, React.Dispatch>];
--------------------------------------------------------------------------------
/web_app/src/types/TemplateMessage.ts:
--------------------------------------------------------------------------------
1 | export type TTemplateTextConfigAlignType = 'left' | 'center' | 'right';
2 |
3 | export type TTemplateMessageType =
4 | 'TEMPLATE_RENDER'
5 | | 'TEMPLATE_RENDER_FINISH'
6 | | 'TEMPLATE_RENDER_ERROR'
7 | | 'TEMPLATE_FULL_CONFIG'
8 | | 'TEMPLATE_SELECT_TEXT_ELEMENT'
9 | | 'TEMPLATE_SELECT_TEXT_CLOSE'
10 | | 'TEMPLATE_DATA'
11 | | 'GET_TEMPLATE_DATA'
12 | | 'GET_CONFIG';
13 |
14 | export type TDesignAppMessageType =
15 | 'SET_DATA' // 设置数据
16 | | 'SET_TEXT_CONFIG' // 设置文字配置
17 | | 'SET_COLOR_MODE' // 设置颜色模式
18 | | 'SET_COLOR_CONFIG' // 设置颜色配置
19 | | 'SET_THEME_COLOR' // 设置主题颜色
20 | | 'SET_BACKGROUND_IMAGE' // 设置背景图片
21 | | 'SET_TITLE_ANIMATE' // 设置标题动画
22 | | 'SET_DURATION' // 设置持续时间
23 | | 'GET_FULL_CONFIG' // 获取全部配置
24 | | 'PREVIEW_MODE' // 预览模式
25 | | 'PLAY_MODE' // 设计器播放模式
26 | | 'SET_OTHER_CONFIG' // 设置其他配置
27 | | 'RENDER' // 执行渲染
28 | | 'GET_TEMPLATE_DATA' // 获取模板数据
29 | | 'GET_CONFIG' // 获取模版配置
30 | | 'VIDEO_CONFIG_CHANGE' // 视频配置变化
31 | | 'START_ANIMATION' // 暂停&打断图表动画
32 | | 'PAUSE_ANIMATION' // 开始&继续图表动画
33 | ;
34 |
35 | export interface ITemplateSelectTextElement {
36 | color: string;
37 | value: string;
38 | display: boolean;
39 | fontSize: number;
40 | align: TTemplateTextConfigAlignType;
41 | width: number;
42 | height: number;
43 | x: number;
44 | y: number;
45 | key: string;
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/web_app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------