mirror of
https://ghproxy.com/https://github.com/StreakingMan/solvable-sheep-game
synced 2025-07-07 21:57:09 +08:00
feat: 自定义主题
This commit is contained in:
parent
e287398572
commit
e35ddfa44e
|
@ -25,5 +25,6 @@ module.exports = {
|
|||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'@typescript-eslint/ban-ts-comment': 0,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
"release": "standard-version --commit-all"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.3.2",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
@ -30,6 +32,7 @@
|
|||
"husky": "^8.0.1",
|
||||
"lint-staged": "^13.0.3",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.55.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"stylelint": "^14.11.0",
|
||||
"stylelint-config-prettier-scss": "^0.0.1",
|
||||
|
|
|
@ -10,21 +10,23 @@
|
|||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.scene-container {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
margin: 10% 0;
|
||||
}
|
||||
.scene {
|
||||
&-container {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
margin: 10% 0;
|
||||
}
|
||||
|
||||
.scene-inner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
overflow: visible;
|
||||
font-size: 28px;
|
||||
&-inner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
overflow: visible;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.symbol {
|
||||
|
@ -35,28 +37,28 @@
|
|||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.symbol-inner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #444;
|
||||
transition: 0.3s;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
&-inner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #444;
|
||||
transition: 0.3s;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
.symbol-inner img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.queue-container {
|
||||
|
@ -67,25 +69,6 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
|
@ -108,3 +91,8 @@
|
|||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.zhenghuo-button {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
74
src/App.tsx
74
src/App.tsx
|
@ -6,9 +6,14 @@ import React, {
|
|||
useState,
|
||||
} from 'react';
|
||||
|
||||
import './App.css';
|
||||
import './App.scss';
|
||||
import { GithubIcon } from './components/GithubIcon';
|
||||
import { parseThemePath, randomString, waitTimeout } from './utils';
|
||||
import {
|
||||
parsePathCustomTheme,
|
||||
parsePathThemeName,
|
||||
randomString,
|
||||
waitTimeout,
|
||||
} from './utils';
|
||||
import { defaultTheme } from './themes/default';
|
||||
import { Icon, Theme } from './themes/interface';
|
||||
import { fishermanTheme } from './themes/fisherman';
|
||||
|
@ -18,9 +23,10 @@ import { pddTheme } from './themes/pdd';
|
|||
import { BeiAn } from './components/BeiAn';
|
||||
import { Info } from './components/Info';
|
||||
import { owTheme } from './themes/ow';
|
||||
import { ConfigDialog } from './components/ConfigDialog';
|
||||
|
||||
// 主题
|
||||
const themes: Theme<any>[] = [
|
||||
// 内置主题
|
||||
const builtInThemes: Theme<any>[] = [
|
||||
defaultTheme,
|
||||
fishermanTheme,
|
||||
jinlunTheme,
|
||||
|
@ -156,8 +162,15 @@ const Symbol: FC<SymbolProps> = ({ x, y, icon, isCover, status, onClick }) => {
|
|||
style={{ opacity: isCover ? 0.5 : 1 }}
|
||||
>
|
||||
{typeof icon.content === 'string' ? (
|
||||
<i>{icon.content}</i>
|
||||
icon.content.startsWith('http') ? (
|
||||
/*图片外链*/
|
||||
<img src={icon.content} alt="" />
|
||||
) : (
|
||||
/*字符表情*/
|
||||
<i>{icon.content}</i>
|
||||
)
|
||||
) : (
|
||||
/*ReactNode*/
|
||||
icon.content
|
||||
)}
|
||||
</div>
|
||||
|
@ -166,12 +179,12 @@ const Symbol: FC<SymbolProps> = ({ x, y, icon, isCover, status, onClick }) => {
|
|||
};
|
||||
|
||||
// 从url初始化主题
|
||||
const themeFromPath: string = parseThemePath(location.href);
|
||||
const themeFromPath: string = parsePathThemeName(location.href);
|
||||
const customThemeFromPath = parsePathCustomTheme(location.href);
|
||||
|
||||
const App: FC = () => {
|
||||
const [curTheme, setCurTheme] = useState<Theme<any>>(
|
||||
themes.find((theme) => theme.name === themeFromPath) ?? defaultTheme
|
||||
);
|
||||
const [curTheme, setCurTheme] = useState<Theme<any>>(defaultTheme);
|
||||
const [themes, setThemes] = useState<Theme<any>[]>(builtInThemes);
|
||||
|
||||
const [scene, setScene] = useState<Scene>(makeScene(1, curTheme.icons));
|
||||
const [level, setLevel] = useState<number>(1);
|
||||
|
@ -182,6 +195,7 @@ const App: FC = () => {
|
|||
const [finished, setFinished] = useState<boolean>(false);
|
||||
const [tipText, setTipText] = useState<string>('');
|
||||
const [animating, setAnimating] = useState<boolean>(false);
|
||||
const [configDialogShow, setConfigDialogShow] = useState<boolean>(false);
|
||||
|
||||
// 音效
|
||||
const soundRefMap = useRef<Record<string, HTMLAudioElement>>({});
|
||||
|
@ -200,6 +214,21 @@ const App: FC = () => {
|
|||
}
|
||||
}, [bgmOn]);
|
||||
|
||||
// 初始化主题
|
||||
useEffect(() => {
|
||||
if (customThemeFromPath) {
|
||||
// 自定义主题
|
||||
setThemes([...themes, customThemeFromPath]);
|
||||
setCurTheme(customThemeFromPath);
|
||||
} else if (themeFromPath) {
|
||||
// 内置主题
|
||||
setCurTheme(
|
||||
themes.find((theme) => theme.name === themeFromPath) ??
|
||||
defaultTheme
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 主题切换
|
||||
useEffect(() => {
|
||||
// 初始化时不加载bgm
|
||||
|
@ -210,7 +239,8 @@ const App: FC = () => {
|
|||
}, 300);
|
||||
}
|
||||
restart();
|
||||
// 更改路径
|
||||
// 更改路径query
|
||||
if (customThemeFromPath) return;
|
||||
history.pushState(
|
||||
{},
|
||||
curTheme.title,
|
||||
|
@ -420,12 +450,17 @@ const App: FC = () => {
|
|||
setAnimating(false);
|
||||
};
|
||||
|
||||
// 自定义整活
|
||||
const customZhenghuo = (theme: Theme<string>) => {
|
||||
setCurTheme(theme);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{curTheme.title}</h2>
|
||||
<h6>
|
||||
<p>
|
||||
<GithubIcon />
|
||||
</h6>
|
||||
</p>
|
||||
<h3 className="flex-container flex-center">
|
||||
主题:
|
||||
{/*TODO themes维护方式调整*/}
|
||||
|
@ -486,10 +521,18 @@ const App: FC = () => {
|
|||
{/*<button onClick={test}>测试</button>*/}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setConfigDialogShow(true)}
|
||||
className="zhenghuo-button primary"
|
||||
>
|
||||
我要整活
|
||||
</button>
|
||||
|
||||
<Info />
|
||||
|
||||
<BeiAn />
|
||||
|
||||
{/*提示弹窗*/}
|
||||
{finished && (
|
||||
<div className="modal">
|
||||
<h1>{tipText}</h1>
|
||||
|
@ -497,6 +540,13 @@ const App: FC = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/*自定义主题弹窗*/}
|
||||
<ConfigDialog
|
||||
show={configDialogShow}
|
||||
closeMethod={() => setConfigDialogShow(false)}
|
||||
previewMethod={customZhenghuo}
|
||||
/>
|
||||
|
||||
{/*bgm*/}
|
||||
<button className="bgm-button" onClick={() => setBgmOn(!bgmOn)}>
|
||||
{bgmOn ? '🔊' : '🔈'}
|
||||
|
|
100
src/components/ConfigDialog.module.scss
Normal file
100
src/components/ConfigDialog.module.scss
Normal file
|
@ -0,0 +1,100 @@
|
|||
.dialog {
|
||||
text-align: left;
|
||||
|
||||
&Wrapper {
|
||||
z-index: 10;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
width: calc(100% - 32px);
|
||||
max-width: 500px;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%) translateY(-100%);
|
||||
opacity: 0;
|
||||
background-color: white;
|
||||
transition: 0.3s;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
margin: 36px 0;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 19px 38px rgb(0 0 0 / 30%),
|
||||
0 15px 12px rgb(0 0 0 / 22%);
|
||||
}
|
||||
}
|
||||
|
||||
&Show {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: crimson;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.add {
|
||||
&Btn {
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 1px solid gray;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&Empty::before {
|
||||
content: '+';
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
&Dialog {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 90%;
|
||||
padding: 12px;
|
||||
transform: translateX(-50%) translateY(-60vh);
|
||||
opacity: 0;
|
||||
transition: 0.3s;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #535bf2;
|
||||
|
||||
&Show {
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delete {
|
||||
&Btn {
|
||||
flex-grow: 1;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f9f9f9;
|
||||
font-size: 1.5em;
|
||||
color: #999;
|
||||
|
||||
span {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
532
src/components/ConfigDialog.tsx
Normal file
532
src/components/ConfigDialog.tsx
Normal file
|
@ -0,0 +1,532 @@
|
|||
import React, { FC, useEffect, useRef, useState } from 'react';
|
||||
import style from './ConfigDialog.module.scss';
|
||||
import classNames from 'classnames';
|
||||
import { Icon, Sound, Theme } from '../themes/interface';
|
||||
import { defaultSounds } from '../themes/default';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
|
||||
const STORAGEKEY = 'customTheme';
|
||||
let storageTheme: Theme<any>;
|
||||
try {
|
||||
const configString = localStorage.getItem(STORAGEKEY);
|
||||
if (configString) {
|
||||
const parseRes = JSON.parse(configString);
|
||||
if (typeof parseRes === 'object') storageTheme = parseRes;
|
||||
}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
|
||||
export const ConfigDialog: FC<{
|
||||
show: boolean;
|
||||
closeMethod: () => void;
|
||||
previewMethod: (theme: Theme<string>) => void;
|
||||
}> = ({ show, closeMethod, previewMethod }) => {
|
||||
const [sounds, setSounds] = useState<Sound[]>([]);
|
||||
const [icons, setIcons] = useState<Icon[]>([]);
|
||||
const inputRefMap = useRef<
|
||||
Record<
|
||||
'name' | 'link' | 'clickSound' | 'tripleSound' | string,
|
||||
HTMLInputElement | HTMLSelectElement
|
||||
>
|
||||
>({});
|
||||
const [configError, setConfigError] = useState<string>('');
|
||||
const [customThemeInfo, setCustomThemeInfo] = useState<{
|
||||
title: string;
|
||||
desc?: string;
|
||||
bgm?: string;
|
||||
}>({ title: '', desc: '', bgm: '' });
|
||||
const [addDialog, setAddDialog] = useState<{
|
||||
show: boolean;
|
||||
type: 'sound' | 'icon';
|
||||
iconForm?: Icon;
|
||||
soundForm?: Sound;
|
||||
error: string;
|
||||
idx?: number;
|
||||
}>({
|
||||
show: false,
|
||||
type: 'sound',
|
||||
error: '',
|
||||
});
|
||||
const [genLink, setGenLink] = useState<string>('');
|
||||
|
||||
// 初始化
|
||||
useEffect(() => {
|
||||
if (storageTheme) {
|
||||
const { title, desc, bgm, sounds, icons } = storageTheme;
|
||||
setSounds(
|
||||
sounds.filter(
|
||||
(s) => !['triple', 'button-click'].includes(s.name)
|
||||
)
|
||||
);
|
||||
setIcons(
|
||||
storageTheme.icons.map((icon) => {
|
||||
if (icon.clickSound === 'button-click')
|
||||
icon.clickSound = '';
|
||||
if (icon.tripleSound === 'triple') icon.tripleSound = '';
|
||||
return icon;
|
||||
})
|
||||
);
|
||||
setCustomThemeInfo({
|
||||
title,
|
||||
// @ts-ignore
|
||||
desc,
|
||||
bgm,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 音效保存
|
||||
const saveSound = (sound: Sound, idx?: number) => {
|
||||
if (!sound.src.startsWith('http')) return '请输入http/https链接';
|
||||
const newSounds = sounds.slice();
|
||||
const newIcons = icons.slice();
|
||||
if (idx != null) {
|
||||
// 编辑
|
||||
for (let i = 0; i < sounds.length; i++) {
|
||||
if (sounds[i].name === sound.name && i !== idx) {
|
||||
return '名称已存在';
|
||||
}
|
||||
}
|
||||
// 检查编辑的音效是否有引用并修改
|
||||
const oldSoundName = sounds[idx].name;
|
||||
for (const icon of newIcons) {
|
||||
if (icon.clickSound === oldSoundName)
|
||||
icon.clickSound = sound.name;
|
||||
if (icon.tripleSound === oldSoundName)
|
||||
icon.tripleSound = sound.name;
|
||||
}
|
||||
newSounds[idx] = sound;
|
||||
} else {
|
||||
// 新增
|
||||
if (sounds.find((s) => s.name === sound.name)) return '名称已存在';
|
||||
newSounds.push(sound);
|
||||
}
|
||||
setIcons(newIcons);
|
||||
setSounds(newSounds);
|
||||
};
|
||||
const onSoundClick = (idx?: number) => {
|
||||
if (addDialog.show) return;
|
||||
setAddDialog({
|
||||
idx,
|
||||
show: true,
|
||||
type: 'sound',
|
||||
soundForm: {
|
||||
name: '',
|
||||
src: '',
|
||||
},
|
||||
error: '',
|
||||
});
|
||||
};
|
||||
|
||||
// 图片保存
|
||||
const saveIcon = (icon: Icon, idx?: number) => {
|
||||
if (
|
||||
typeof icon.content !== 'string' ||
|
||||
!icon.content?.startsWith('http')
|
||||
)
|
||||
return '请输入http/https链接';
|
||||
const newIcons = icons.slice();
|
||||
if (idx != null) {
|
||||
// 编辑
|
||||
for (let i = 0; i < icons.length; i++) {
|
||||
if (icons[i].name === icon.name && i !== idx) {
|
||||
return '名称已存在';
|
||||
}
|
||||
}
|
||||
newIcons[idx] = icon;
|
||||
} else {
|
||||
// 新增
|
||||
if (icons.find((i) => i.name === icon.name)) return '名称已存在';
|
||||
newIcons.push(icon);
|
||||
}
|
||||
setIcons(newIcons);
|
||||
};
|
||||
const onIconClick = (idx?: number) => {
|
||||
if (addDialog.show) return;
|
||||
setAddDialog({
|
||||
idx,
|
||||
show: true,
|
||||
type: 'icon',
|
||||
iconForm:
|
||||
idx != null
|
||||
? { ...icons[idx] }
|
||||
: {
|
||||
name: '',
|
||||
content: '',
|
||||
tripleSound: '',
|
||||
clickSound: '',
|
||||
},
|
||||
error: '',
|
||||
});
|
||||
};
|
||||
|
||||
// 回显
|
||||
useEffect(() => {
|
||||
const { show, type, idx } = addDialog;
|
||||
if (show) return;
|
||||
if (!inputRefMap.current) return;
|
||||
if (type === 'icon') {
|
||||
inputRefMap.current.name.value = idx != null ? icons[idx].name : '';
|
||||
inputRefMap.current.link.value =
|
||||
idx != null ? (icons[idx].content as string) : '';
|
||||
inputRefMap.current.clickSound.value =
|
||||
idx != null ? icons[idx]?.clickSound || '' : '';
|
||||
inputRefMap.current.tripleSound.value =
|
||||
idx != null ? icons[idx]?.tripleSound || '' : '';
|
||||
} else {
|
||||
inputRefMap.current.name.value =
|
||||
idx != null ? sounds[idx].name : '';
|
||||
inputRefMap.current.link.value = idx != null ? sounds[idx].src : '';
|
||||
}
|
||||
}, [addDialog]);
|
||||
|
||||
// 添加单项的点击
|
||||
const onAddDialogSaveClick = () => {
|
||||
const error = (addDialog.type === 'sound' ? saveSound : saveIcon)(
|
||||
addDialog[`${addDialog.type}Form`] as any,
|
||||
addDialog.idx
|
||||
);
|
||||
if (error) {
|
||||
setAddDialog({ ...addDialog, error });
|
||||
} else {
|
||||
closeAddDialog();
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭添加弹窗
|
||||
const closeAddDialog = () => {
|
||||
setAddDialog({ ...addDialog, show: false });
|
||||
};
|
||||
|
||||
// 生成主题
|
||||
const generateTheme: () => Promise<Theme<any>> = async () => {
|
||||
const { title, desc, bgm } = customThemeInfo;
|
||||
if (bgm && bgm.startsWith('http'))
|
||||
return Promise.reject('bgm请输入http/https链接');
|
||||
if (!title) return Promise.reject('请填写标题');
|
||||
if (icons.length !== 10) return Promise.reject('图片素材需要提供10张');
|
||||
|
||||
let hasDefaultMaterial = false;
|
||||
const customIcons = icons.map((icon) => {
|
||||
if (!icon.clickSound) {
|
||||
hasDefaultMaterial = true;
|
||||
icon.clickSound = 'button-click';
|
||||
}
|
||||
if (!icon.tripleSound) {
|
||||
hasDefaultMaterial = true;
|
||||
icon.tripleSound = 'triple';
|
||||
}
|
||||
return { ...icon };
|
||||
});
|
||||
const customSounds = sounds.map((sounds) => ({ ...sounds }));
|
||||
if (hasDefaultMaterial) {
|
||||
customSounds.push(...defaultSounds);
|
||||
}
|
||||
|
||||
const customTheme: Theme<any> = {
|
||||
name: `自定义-${title}`,
|
||||
title,
|
||||
desc,
|
||||
bgm,
|
||||
icons: customIcons,
|
||||
sounds: customSounds,
|
||||
};
|
||||
|
||||
return Promise.resolve(customTheme);
|
||||
};
|
||||
|
||||
// 预览
|
||||
const onPreviewClick = () => {
|
||||
setConfigError('');
|
||||
generateTheme()
|
||||
.then((theme) => {
|
||||
previewMethod(theme);
|
||||
localStorage.setItem(STORAGEKEY, JSON.stringify(theme));
|
||||
closeMethod();
|
||||
})
|
||||
.catch((e) => {
|
||||
setConfigError(e);
|
||||
});
|
||||
};
|
||||
|
||||
// 生成二维码和链接
|
||||
const onGenQrLinkClick = () => {
|
||||
setConfigError('');
|
||||
setGenLink('');
|
||||
generateTheme()
|
||||
.then((theme) => {
|
||||
const stringify = JSON.stringify(theme);
|
||||
localStorage.setItem(STORAGEKEY, stringify);
|
||||
const link = `${
|
||||
location.origin
|
||||
}?customTheme=${encodeURIComponent(stringify)}`;
|
||||
setGenLink(link);
|
||||
})
|
||||
.catch((e) => {
|
||||
setConfigError(e);
|
||||
});
|
||||
};
|
||||
|
||||
// 删除按钮
|
||||
const DeleteBtn: FC<{ idx: number; type: 'sound' | 'icon' }> = ({
|
||||
idx,
|
||||
type,
|
||||
}) => {
|
||||
const deleteItem = () => {
|
||||
if (type === 'sound') {
|
||||
const newSounds = sounds.slice();
|
||||
newSounds.splice(idx, 1);
|
||||
setSounds(newSounds);
|
||||
} else {
|
||||
const newIcons = icons.slice();
|
||||
newIcons.splice(idx, 1);
|
||||
setIcons(newIcons);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className={style.deleteBtn} onClick={deleteItem}>
|
||||
<span>+</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
style.dialog,
|
||||
style.dialogWrapper,
|
||||
show && style.dialogShow,
|
||||
'flex-container flex-container'
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
目前自定义仅支持配置链接,可网上自行搜索素材,或者将自己处理好的素材上传第三方存储服务上再复制外链,安利一波七牛云(七牛云打钱)对象存储,
|
||||
白嫖额度基本够用(<strong>但还是需要注意压缩素材</strong>)
|
||||
</p>
|
||||
|
||||
{/*基本配置*/}
|
||||
<h4 className="flex-container flex-center">
|
||||
标题:
|
||||
<input
|
||||
value={customThemeInfo.title}
|
||||
placeholder="必填"
|
||||
className="flex-grow"
|
||||
onChange={(e) =>
|
||||
setCustomThemeInfo({
|
||||
...customThemeInfo,
|
||||
title: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</h4>
|
||||
<h4 className="flex-container flex-center">
|
||||
描述:
|
||||
<input
|
||||
value={customThemeInfo.desc}
|
||||
placeholder="可选"
|
||||
className="flex-grow"
|
||||
onChange={(e) =>
|
||||
setCustomThemeInfo({
|
||||
...customThemeInfo,
|
||||
desc: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</h4>
|
||||
<h4 className="flex-container flex-center">
|
||||
背景音乐:
|
||||
<input
|
||||
value={customThemeInfo.bgm}
|
||||
placeholder="可选 http(s)://example.com/src.audioOrImage"
|
||||
className="flex-grow"
|
||||
onChange={(e) =>
|
||||
setCustomThemeInfo({
|
||||
...customThemeInfo,
|
||||
bgm: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</h4>
|
||||
|
||||
<h4>音效素材</h4>
|
||||
<div className="flex-container">
|
||||
{sounds.map((sound, idx) => (
|
||||
<div
|
||||
className="flex-container flex-column"
|
||||
key={sound.name}
|
||||
>
|
||||
<div
|
||||
onClick={() => onSoundClick(idx)}
|
||||
className={classNames(style.addBtn)}
|
||||
>
|
||||
{sound.name}
|
||||
</div>
|
||||
<DeleteBtn idx={idx} type={'sound'} />
|
||||
</div>
|
||||
))}
|
||||
{sounds.length < 20 && (
|
||||
<div
|
||||
onClick={() => onSoundClick()}
|
||||
className={classNames(style.addBtn, style.addBtnEmpty)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<h4>图片素材 {icons.length}/10</h4>
|
||||
<div className="flex-container">
|
||||
{icons.map((icon, idx) => (
|
||||
<div className="flex-container flex-column" key={icon.name}>
|
||||
<div
|
||||
onClick={() => onIconClick(idx)}
|
||||
className={classNames(style.addBtn)}
|
||||
>
|
||||
{/* @ts-ignore*/}
|
||||
<img src={icon.content} alt="" />
|
||||
</div>
|
||||
<DeleteBtn idx={idx} type={'icon'} />
|
||||
</div>
|
||||
))}
|
||||
{icons.length < 10 && (
|
||||
<div
|
||||
onClick={() => onIconClick()}
|
||||
className={classNames(style.addBtn, style.addBtnEmpty)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-spacer" />
|
||||
{genLink && <textarea value={genLink} />}
|
||||
{configError && <div className={style.error}>{configError}</div>}
|
||||
<div className="flex-container">
|
||||
<button className="flex-grow" onClick={onPreviewClick}>
|
||||
保存并预览
|
||||
</button>
|
||||
<button className="flex-grow" onClick={onGenQrLinkClick}>
|
||||
生成二维码&链接
|
||||
</button>
|
||||
<button className="flex-grow" onClick={closeMethod}>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/*添加弹窗*/}
|
||||
<div
|
||||
className={classNames(
|
||||
style.addDialog,
|
||||
addDialog.show && style.addDialogShow,
|
||||
'flex-container flex-column'
|
||||
)}
|
||||
>
|
||||
<div className="flex-container flex-center">
|
||||
名称:
|
||||
<input
|
||||
ref={(ref) => ref && (inputRefMap.current.name = ref)}
|
||||
className="flex-grow"
|
||||
placeholder="唯一名称"
|
||||
onChange={(e) =>
|
||||
setAddDialog({
|
||||
...addDialog,
|
||||
[`${addDialog.type}Form`]: {
|
||||
...addDialog[`${addDialog.type}Form`],
|
||||
name: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-container flex-center">
|
||||
链接:
|
||||
<input
|
||||
ref={(ref) => ref && (inputRefMap.current.link = ref)}
|
||||
className="flex-grow"
|
||||
placeholder="http(s)://example.com/src.audioOrImage"
|
||||
onChange={(e) =>
|
||||
setAddDialog({
|
||||
...addDialog,
|
||||
[`${addDialog.type}Form`]: {
|
||||
...addDialog[`${addDialog.type}Form`],
|
||||
[addDialog.type === 'sound'
|
||||
? 'src'
|
||||
: 'content']: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{addDialog.type === 'icon' && (
|
||||
<>
|
||||
<div className="flex-container flex-center">
|
||||
点击音效:
|
||||
<select
|
||||
ref={(ref) =>
|
||||
ref &&
|
||||
(inputRefMap.current.clickSound = ref)
|
||||
}
|
||||
className="flex-grow"
|
||||
onChange={(e) =>
|
||||
setAddDialog({
|
||||
...addDialog,
|
||||
/*@ts-ignore*/
|
||||
iconForm: {
|
||||
...addDialog.iconForm,
|
||||
clickSound: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="">默认</option>
|
||||
{sounds.map((s) => (
|
||||
<option key={s.name} value={s.name}>
|
||||
{s.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-container flex-center">
|
||||
三连音效:
|
||||
<select
|
||||
ref={(ref) =>
|
||||
ref &&
|
||||
(inputRefMap.current.tripleSound = ref)
|
||||
}
|
||||
className="flex-grow"
|
||||
onChange={(e) =>
|
||||
setAddDialog({
|
||||
...addDialog,
|
||||
/*@ts-ignore*/
|
||||
iconForm: {
|
||||
...addDialog.iconForm,
|
||||
tripleSound: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="">默认</option>
|
||||
{sounds.map((s) => (
|
||||
<option key={s.name} value={s.name}>
|
||||
{s.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{addDialog.error && (
|
||||
<div className={style.error}>{addDialog.error}</div>
|
||||
)}
|
||||
<div className="flex-container">
|
||||
<button className="flex-grow" onClick={closeAddDialog}>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
className="flex-grow primary"
|
||||
onClick={onAddDialogSaveClick}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
import './styles/global.scss';
|
||||
import './styles/utils.scss';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
|
|
|
@ -34,11 +34,17 @@ h1 {
|
|||
}
|
||||
|
||||
select {
|
||||
border: 2px solid gray;
|
||||
border: 1px solid gray;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid gray;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
|
@ -51,15 +57,15 @@ button {
|
|||
transition: border-color 0.25s;
|
||||
word-break: keep-all;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
&.primary {
|
||||
background-color: #646cff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
&:hover:not(.primary) {
|
||||
border-color: #646cff;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
31
src/styles/utils.scss
Normal file
31
src/styles/utils.scss
Normal file
|
@ -0,0 +1,31 @@
|
|||
.flex-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
|
@ -7,13 +7,14 @@ export interface Icon<T = string> {
|
|||
tripleSound: T;
|
||||
}
|
||||
|
||||
interface Sound<T = string> {
|
||||
export interface Sound<T = string> {
|
||||
name: T;
|
||||
src: string;
|
||||
}
|
||||
|
||||
type Operation = 'shift' | 'undo' | 'wash';
|
||||
|
||||
// TODO title name 冗余
|
||||
export interface Theme<SoundNames> {
|
||||
title: string;
|
||||
desc?: ReactNode;
|
||||
|
|
26
src/utils.ts
26
src/utils.ts
|
@ -1,3 +1,5 @@
|
|||
import { Theme } from './themes/interface';
|
||||
|
||||
export const randomString: (len: number) => string = (len) => {
|
||||
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
let res = '';
|
||||
|
@ -16,8 +18,30 @@ export const waitTimeout: (timeout: number) => Promise<void> = (timeout) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const parseThemePath: (url: string) => string = (url) => {
|
||||
// 从url获取内置主题name
|
||||
export const parsePathThemeName: (url: string) => string = (url) => {
|
||||
const urlObj = new URL(url);
|
||||
const params = urlObj.searchParams;
|
||||
return decodeURIComponent(params.get('theme') || '默认');
|
||||
};
|
||||
|
||||
// 从url解析自定义主题JSON
|
||||
export const parsePathCustomTheme: (url: string) => Theme<string> | null = (
|
||||
url
|
||||
) => {
|
||||
const urlObj = new URL(url);
|
||||
const params = urlObj.searchParams;
|
||||
const customThemeJsonString = params.get('customTheme');
|
||||
if (!customThemeJsonString) return null;
|
||||
try {
|
||||
const parseTheme = JSON.parse(
|
||||
decodeURIComponent(customThemeJsonString)
|
||||
);
|
||||
// TODO 解析内容校验
|
||||
console.log(parseTheme);
|
||||
return parseTheme;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
78
yarn.lock
78
yarn.lock
|
@ -782,6 +782,14 @@ ansi-styles@^6.0.0:
|
|||
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.1.tgz#63cd61c72283a71cb30bd881dbb60adada74bc70"
|
||||
integrity sha512-qDOv24WjnYuL+wbwHdlsYZFy+cgPtrYw0Tn7GLORicQp9BkQLzrgI3Pm4VyR9ERZ41YTn7KlMPuL1n05WdZvmg==
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
arg@^4.1.0:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||
|
@ -843,6 +851,11 @@ balanced-match@^2.0.0:
|
|||
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9"
|
||||
integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
|
@ -851,7 +864,7 @@ brace-expansion@^1.1.7:
|
|||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
braces@^3.0.2:
|
||||
braces@^3.0.2, braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||
|
@ -922,6 +935,26 @@ chalk@^4.0.0, chalk@^4.1.0:
|
|||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
"chokidar@>=3.0.0 <4.0.0":
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
||||
dependencies:
|
||||
anymatch "~3.1.2"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.2"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.6.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
classnames@^2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
|
||||
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||
|
@ -1963,7 +1996,7 @@ gitconfiglocal@^1.0.0:
|
|||
dependencies:
|
||||
ini "^1.3.2"
|
||||
|
||||
glob-parent@^5.1.2:
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
|
@ -2146,6 +2179,11 @@ ignore@^5.2.0:
|
|||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
|
||||
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
|
||||
|
||||
immutable@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
|
||||
integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
|
||||
|
||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
|
@ -2208,6 +2246,13 @@ is-bigint@^1.0.1:
|
|||
dependencies:
|
||||
has-bigints "^1.0.1"
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-boolean-object@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
|
||||
|
@ -2250,7 +2295,7 @@ is-fullwidth-code-point@^4.0.0:
|
|||
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
|
||||
integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
|
||||
|
||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
|
||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||
|
@ -2742,7 +2787,7 @@ normalize-package-data@^3.0.0:
|
|||
semver "^7.3.4"
|
||||
validate-npm-package-license "^3.0.1"
|
||||
|
||||
normalize-path@^3.0.0:
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
@ -2992,7 +3037,7 @@ picocolors@^1.0.0:
|
|||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.3.1:
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
@ -3095,6 +3140,11 @@ q@^1.5.1:
|
|||
resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
|
||||
|
||||
qrcode.react@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8"
|
||||
integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
|
@ -3188,6 +3238,13 @@ readable-stream@~2.3.6:
|
|||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
redent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||
|
@ -3311,6 +3368,15 @@ safe-buffer@~5.2.0:
|
|||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
sass@^1.55.0:
|
||||
version "1.55.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.55.0.tgz#0c4d3c293cfe8f8a2e8d3b666e1cf1bff8065d1c"
|
||||
integrity sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
|
||||
scheduler@^0.23.0:
|
||||
version "0.23.0"
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
|
||||
|
@ -3392,7 +3458,7 @@ slice-ansi@^5.0.0:
|
|||
ansi-styles "^6.0.0"
|
||||
is-fullwidth-code-point "^4.0.0"
|
||||
|
||||
source-map-js@^1.0.2:
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
|
|
Loading…
Reference in New Issue
Block a user