mirror of
https://git.unlock-music.dev/um/um-react.git
synced 2024-11-27 18:22:16 +00:00
feat: improved layout
This commit is contained in:
parent
425ac8e283
commit
1c3b6c45c6
@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^2.0.19",
|
"@chakra-ui/icons": "^2.0.19",
|
||||||
"@chakra-ui/react": "^2.6.1",
|
"@chakra-ui/react": "^2.7.0",
|
||||||
"@emotion/react": "^11.11.0",
|
"@emotion/react": "^11.11.0",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@jixun/libparakeet": "0.1.1",
|
"@jixun/libparakeet": "0.1.1",
|
||||||
|
1659
pnpm-lock.yaml
1659
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -32,11 +32,11 @@ export function AppRoot() {
|
|||||||
</Tab>
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanels overflow="auto" minW={0}>
|
<TabPanels overflow="auto" minW={0} flexDir="column" flex={1} display="flex">
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<MainTab />
|
<MainTab />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel flex={1} display="flex">
|
||||||
<SettingsTab />
|
<SettingsTab />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
|
20
src/features/settings/Settings.tsx
Normal file
20
src/features/settings/Settings.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Tab, TabList, TabPanel, TabPanels, Tabs, Text } from '@chakra-ui/react';
|
||||||
|
import { PanelQMC } from './panels/PanelQMC';
|
||||||
|
|
||||||
|
export function Settings() {
|
||||||
|
return (
|
||||||
|
<Tabs orientation="vertical" align="start" variant="line-i" flex={1}>
|
||||||
|
<TabList minW={0} width="8em" textAlign="right" justifyContent="center">
|
||||||
|
<Tab>QQ 音乐</Tab>
|
||||||
|
<Tab>其它</Tab>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
|
<TabPanels>
|
||||||
|
<PanelQMC />
|
||||||
|
<TabPanel>
|
||||||
|
<Text>待定</Text>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
153
src/features/settings/panels/PanelQMC.tsx
Normal file
153
src/features/settings/panels/PanelQMC.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Flex,
|
||||||
|
HStack,
|
||||||
|
Heading,
|
||||||
|
Icon,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputLeftElement,
|
||||||
|
InputRightElement,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuDivider,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
Spacer,
|
||||||
|
TabPanel,
|
||||||
|
Text,
|
||||||
|
VStack,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { selectQM2CSettings } from '../settingsSlice';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { produce } from 'immer';
|
||||||
|
import { MdAdd, MdDeleteForever, MdExpandMore, MdFileUpload, MdVpnKey } from 'react-icons/md';
|
||||||
|
|
||||||
|
interface InternalQMCKeys {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PanelQMC() {
|
||||||
|
const qmcSettings = useSelector(selectQM2CSettings);
|
||||||
|
const [qmcKeys, setQMCKeys] = useState<InternalQMCKeys[]>([]);
|
||||||
|
const resetQmcKeys = () => {
|
||||||
|
const result: InternalQMCKeys[] = [];
|
||||||
|
for (const [name, key] of Object.entries(qmcSettings.keys)) {
|
||||||
|
result.push({ id: name, name, key });
|
||||||
|
}
|
||||||
|
setQMCKeys(result);
|
||||||
|
};
|
||||||
|
const addRow = () => {
|
||||||
|
setQMCKeys((prev) => [...prev, { id: nanoid(), key: '', name: '' }]);
|
||||||
|
};
|
||||||
|
const updateKey = (prop: 'name' | 'key', id: string, e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setQMCKeys((prev) =>
|
||||||
|
produce(prev, (draft) => {
|
||||||
|
const item = draft.find((item) => item.id === id);
|
||||||
|
if (item) {
|
||||||
|
item[prop] = e.target.value;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const applyChanges = () => {
|
||||||
|
//
|
||||||
|
};
|
||||||
|
const clearAll = () => setQMCKeys([]);
|
||||||
|
useEffect(resetQmcKeys, [qmcSettings.keys]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex as={TabPanel} flexDir="column" h="100%">
|
||||||
|
<Box flex={1} minH={0} overflow="auto">
|
||||||
|
<Heading as="h2" size="lg">
|
||||||
|
密钥
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<Box p="4" pr="0" borderStart="2px solid" borderColor="gray.200">
|
||||||
|
<List spacing={3}>
|
||||||
|
{qmcKeys.map(({ id, key, name }, i) => (
|
||||||
|
<ListItem key={id}>
|
||||||
|
<HStack>
|
||||||
|
<Text w="2em" textAlign="center">
|
||||||
|
{i + 1}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<VStack flex={1}>
|
||||||
|
<Input
|
||||||
|
variant="flushed"
|
||||||
|
placeholder="文件名"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => updateKey('name', id, e)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputGroup size="xs">
|
||||||
|
<InputLeftElement pr="2">
|
||||||
|
<Icon as={MdVpnKey} />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
variant="flushed"
|
||||||
|
placeholder="密钥"
|
||||||
|
value={key}
|
||||||
|
onChange={(e) => updateKey('key', id, e)}
|
||||||
|
/>
|
||||||
|
<InputRightElement>
|
||||||
|
<Text pl="2" color={key.length ? 'green.500' : 'red.500'}>
|
||||||
|
<code>{key.length || '?'}</code>
|
||||||
|
</Text>
|
||||||
|
</InputRightElement>
|
||||||
|
</InputGroup>
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
{qmcKeys.length === 0 && <Text>还没有添加密钥。</Text>}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<VStack mt="4" alignItems="flex-start">
|
||||||
|
<Text>密钥填充完毕后,按下「应用」来使用新的设置。</Text>
|
||||||
|
<Flex flexDir="row" gap="2" w="full">
|
||||||
|
<Box>
|
||||||
|
<ButtonGroup isAttached variant="outline">
|
||||||
|
<Button onClick={addRow}>
|
||||||
|
<Icon as={MdAdd} />
|
||||||
|
添加密钥
|
||||||
|
</Button>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={IconButton} icon={<MdExpandMore />}>
|
||||||
|
<Icon as={MdExpandMore} />1
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem onClick={() => alert('TODO!')} icon={<Icon as={MdFileUpload} h={18} w={18} />}>
|
||||||
|
从文件导入 (JSON)
|
||||||
|
</MenuItem>
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuItem color="red" onClick={clearAll} icon={<Icon as={MdDeleteForever} h={18} w={18} />}>
|
||||||
|
清空
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Box>
|
||||||
|
<Spacer />
|
||||||
|
<Box>
|
||||||
|
<Button onClick={resetQmcKeys} colorScheme="red" variant="ghost">
|
||||||
|
放弃
|
||||||
|
</Button>
|
||||||
|
<Button onClick={applyChanges}>应用</Button>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</VStack>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import type { RootState } from '~/store';
|
||||||
|
|
||||||
export interface QMCSettings {
|
export interface QMCSettings {
|
||||||
keys: Record<string, string>; // { [fileName]: ekey }
|
keys: Record<string, string>; // { [fileName]: ekey }
|
||||||
@ -28,4 +29,6 @@ export const settingsSlice = createSlice({
|
|||||||
|
|
||||||
export const { updateSettings, resetConfig } = settingsSlice.actions;
|
export const { updateSettings, resetConfig } = settingsSlice.actions;
|
||||||
|
|
||||||
|
export const selectQM2CSettings = (state: RootState) => state.settings.qmc2;
|
||||||
|
|
||||||
export default settingsSlice.reducer;
|
export default settingsSlice.reducer;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import { Container, Flex } from '@chakra-ui/react';
|
||||||
|
import { Settings } from '~/features/settings/Settings';
|
||||||
|
|
||||||
export function SettingsTab() {
|
export function SettingsTab() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Container as={Flex} maxW="container.lg">
|
||||||
<p>Hallo</p>
|
<Settings />
|
||||||
<p>Thank you, thank you very much.</p>
|
</Container>
|
||||||
<p>Ha-Halo, thank you</p>
|
|
||||||
<p>Thank you very much!</p>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
12
src/theme.ts
12
src/theme.ts
@ -1,4 +1,5 @@
|
|||||||
import { extendTheme } from '@chakra-ui/react';
|
import { extendTheme } from '@chakra-ui/react';
|
||||||
|
import { tabsTheme } from './themes/Tabs';
|
||||||
|
|
||||||
export const theme = extendTheme({
|
export const theme = extendTheme({
|
||||||
fonts: {
|
fonts: {
|
||||||
@ -18,6 +19,17 @@ export const theme = extendTheme({
|
|||||||
colorScheme: 'teal',
|
colorScheme: 'teal',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Tabs: tabsTheme,
|
||||||
|
Heading: {
|
||||||
|
baseStyle: {
|
||||||
|
userSelect: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Text: {
|
||||||
|
baseStyle: {
|
||||||
|
userSelect: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
styles: {
|
styles: {
|
||||||
global: {
|
global: {
|
||||||
|
64
src/themes/Tabs.tsx
Normal file
64
src/themes/Tabs.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { tabsAnatomy } from '@chakra-ui/anatomy';
|
||||||
|
import { createMultiStyleConfigHelpers, cssVar } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const $fg = cssVar('tabs-color');
|
||||||
|
const $bg = cssVar('tabs-bg');
|
||||||
|
|
||||||
|
const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(tabsAnatomy.keys);
|
||||||
|
|
||||||
|
const variantLineInvert = definePartsStyle((props) => {
|
||||||
|
const { colorScheme: c, orientation } = props;
|
||||||
|
const isVertical = orientation === 'vertical';
|
||||||
|
const borderProp = isVertical ? 'borderEnd' : 'borderTop';
|
||||||
|
const marginProp = isVertical ? 'marginEnd' : 'marginTop';
|
||||||
|
|
||||||
|
return {
|
||||||
|
tablist: {
|
||||||
|
[borderProp]: '2px solid',
|
||||||
|
borderColor: 'inherit',
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
[borderProp]: '2px solid',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
[marginProp]: '-2px',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
_selected: {
|
||||||
|
[$fg.variable]: `colors.${c}.600`,
|
||||||
|
_dark: {
|
||||||
|
[$fg.variable]: `colors.${c}.300`,
|
||||||
|
},
|
||||||
|
borderColor: 'currentColor',
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
[$bg.variable]: 'colors.gray.200',
|
||||||
|
_dark: {
|
||||||
|
[$bg.variable]: 'colors.whiteAlpha.300',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
_active: { bg: 'none' },
|
||||||
|
},
|
||||||
|
color: $fg.reference,
|
||||||
|
bg: $bg.reference,
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tabsTheme = defineMultiStyleConfig({
|
||||||
|
baseStyle: {
|
||||||
|
tablist: {
|
||||||
|
userSelect: 'none',
|
||||||
|
},
|
||||||
|
tabpanel: {
|
||||||
|
minHeight: 0,
|
||||||
|
overflow: 'auto',
|
||||||
|
maxHeight: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
'line-i': variantLineInvert,
|
||||||
|
},
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user