diff --git a/components/contentMenu.tsx b/components/contentMenu.tsx index 37b02c8..6bc52d5 100644 --- a/components/contentMenu.tsx +++ b/components/contentMenu.tsx @@ -3,18 +3,23 @@ interface ContentItem { color?: string; } -interface TextContentItem extends ContentItem { +interface TextCI extends ContentItem { type: 'text'; text: string; onClick: React.MouseEventHandler; } -interface SeparatorContentItem extends ContentItem { +interface SeparatorCI extends ContentItem { type: 'separator'; height: number; } -type Content = TextContentItem | SeparatorContentItem; +interface TitleCI extends ContentItem { + type: 'title'; + text: string; +} + +type Content = TextCI | SeparatorCI | TitleCI; export interface ContentMenuSet { x: number; @@ -27,11 +32,12 @@ export default function ContentMenu({ set }: { set: ContentMenuSet }) { let array = set.content.map((v, i) => { const { color = '#000' } = v; if (v.type === 'text') return ( -
+

{v.text}

); - if (v.type === 'separator') return
; + if (v.type === 'separator') return (
); + if (v.type === 'title') return (

{v.text}

); }); return (<> {set.display && (
{array}
)} diff --git a/components/i18n/config/chat.ts b/components/i18n/config/chat.ts index 3a9c98b..ffd7445 100644 --- a/components/i18n/config/chat.ts +++ b/components/i18n/config/chat.ts @@ -103,5 +103,21 @@ const chat = { 'zh-CN': '编辑', 'zh-TW': '編輯', }, + idConfirmTitle: { + 'zh-CN': '确定删除', + 'zh-TW': '確定刪除', + }, + idConfirmInfo: { + 'zh-CN': '确定删除与$0的聊天吗', + 'zh-TW': '確定刪除於$0的聊天嗎', + }, + confirm: { + 'zh-CN': '确定', + 'zh-TW': '確定', + }, + cancel: { + 'zh-CN': '取消', + 'zh-TW': '取消', + }, } export default chat; \ No newline at end of file diff --git a/components/i18n/index.ts b/components/i18n/index.ts index 0fda5f4..8ad5386 100644 --- a/components/i18n/index.ts +++ b/components/i18n/index.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { useSetting } from "../setting"; +import { getSettingFun } from "../setting"; export type Locales = 'zh-CN' | 'zh-TW'; @@ -13,7 +13,7 @@ export function fillBlank(i18n: string, ...fills: (string | undefined)[]): strin export function useLocale(i18n: T) { const [lo, setLo] = useState(''); - const { getSetting } = useSetting(); + const { getSetting } = getSettingFun(); useEffect(() => setLo(getSetting().locale || window.navigator.language), []); function locale(key: K, loc?: string) { return (i18n as Record>)[key][loc || lo]; diff --git a/components/main.tsx b/components/main.tsx index b04922a..a138e97 100644 --- a/components/main.tsx +++ b/components/main.tsx @@ -1,6 +1,6 @@ //Components import Link from 'next/link'; -import { SettingForm, useSetting } from '@/components/setting'; +import { SettingForm, getSettingFun } from '@/components/setting'; //Styles import styles from '@/styles/MainNode.module.scss'; @@ -9,9 +9,9 @@ import styles from '@/styles/MainNode.module.scss'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import Window, { AllWindow, AllWindows, getWindowFun } from './window'; -import { Locales, i18nContents, useLocale } from './i18n'; +import { Locales, useLocale } from './i18n'; import main from './i18n/config/main'; -import { SettingState, SettingArg } from '@/components/setting'; +import { Settings, SettingArg } from '@/components/setting'; function MomoTalkIcon() { return ( @@ -37,7 +37,7 @@ function MTBarLink({ type }: { type: string }) { ); } -function MTStart({ animation }: { animation: SettingState['animation'] }) { +function MTStart() { const [ani, setAni] = useState<'true' | 'false'>('false'); useEffect(() => { const { animation } = window.sessionStorage; @@ -48,6 +48,12 @@ function MTStart({ animation }: { animation: SettingState['animation'] }) { } }, []); + const [animation, setAnimation] = useState(); + const { getSetting } = getSettingFun(); + useEffect(() => { + setAnimation(getSetting().animation); + }, []); + const userAni = (() => { if (animation === 'none' || animation === undefined) return 'false'; else if (animation === 'first') return ani; @@ -69,18 +75,13 @@ export default function MainNode({ children, onBodyClick }: { }) { const { lo, locale, localeType } = useLocale(main); - const { setting, setSetting } = useSetting({ - locale: lo, - animation: 'first', - fileName: 'untitled', - }); + const { setSetting, windowOnload } = getSettingFun(); + useEffect(windowOnload, []); - const { - allWindow, - addNewWindow, - openWindow, - closeWindow, - } = getWindowFun(useState({ all: [], component: {} })); + const { allWindow, addNewWindow, openWindow, closeWindow } = getWindowFun(useState({ + all: [], + component: {}, + })); const Setting = new Window('Setting'); @@ -116,7 +117,7 @@ export default function MainNode({ children, onBodyClick }: { }} done={locale('done')} onSubmit={data => { - setSetting(data as SettingState); + setSetting(data as Settings); close(); }} /> @@ -130,7 +131,7 @@ export default function MainNode({ children, onBodyClick }: { return (
- +
diff --git a/components/setting.tsx b/components/setting.tsx index 7d216a0..76e7cb5 100644 --- a/components/setting.tsx +++ b/components/setting.tsx @@ -77,32 +77,28 @@ export function SettingForm({ option, done, onSubmit }: Settin ) } -export interface SettingState { +export interface Settings { locale?: string; animation?: 'none' | 'first' | 'every'; fileName?: string; } export interface SettingArg { - setting: SettingState; - setSetting: SetStateFun; + setting: Settings; + setSetting: SetStateFun; } -export function useSetting(state?: SettingState) { - const [setting, setSetting] = getClassState( - useState(state || {}), - newSet => { - window.localStorage.set = JSON.stringify(newSet) - } - ); - useEffect(() => { - let setting: string = window.localStorage.set; - if (setting !== undefined) { - setSetting(JSON.parse(setting)); - } - }, []); - function getSetting(): SettingState { - return JSON.parse(window.localStorage.set); +export function getSettingFun(defaultSet?: Settings) { + function getSetting(): Settings { + return JSON.parse(window.localStorage.set as string); } - return { setting, setSetting, getSetting }; + function setSetting(newSet: Partial, first?: boolean): void { + const preSet = first ? {} : getSetting(); + //console.log({ preSet, newSet, localStorage: JSON.parse(localStorage.set) }); + window.localStorage.set = JSON.stringify({ ...preSet, ...newSet }); + } + function windowOnload() { + if (window.localStorage.set === undefined && defaultSet !== undefined) setSetting(defaultSet, true); + } + return { getSetting, setSetting, windowOnload }; } \ No newline at end of file diff --git a/components/students/index.tsx b/components/students/index.tsx index a9b7fe1..6f412d8 100644 --- a/components/students/index.tsx +++ b/components/students/index.tsx @@ -3,16 +3,25 @@ import { getStudentInfo } from './studentsMethods'; import ImgCol from '../imgCol'; interface StudentProps { - id: number, - allInfo: studentsJson, - onClick: React.MouseEventHandler, - select: boolean, + id: number; + allInfo: studentsJson; + onClick: React.MouseEventHandler; + select: boolean; + onContentMenu?: React.MouseEventHandler; } -export default function Student({ id, allInfo, onClick, select }: StudentProps) { +export default function Student({ id, allInfo, onClick, select, onContentMenu }: StudentProps) { const info = getStudentInfo(allInfo, id); return ( -
+
{ + e.preventDefault(); + onContentMenu?.(e); + }} + className={select ? 'select' : ''} + title={`id:${id}`} + > ({ student: 0, studentsList: [10000, 10045], @@ -327,6 +331,9 @@ export default function Chat() { const sendMessageInputRef = useRef(''); const SendMessage = new Window('SendMessage'); + //Confirm + const IdConfirm = new Window('IdConfirm'); + useEffect(() => { addNewWindow(IdPrompt, (zIndex, id, display, { studentsList, type }, all) => ( )); + addNewWindow(IdConfirm, (zIndex, id, display, { student, textInfo, studentsList }, all) => ( + closeWindow(all, id)} + element={close => (<> +

{fillBlank(locale('idConfirmInfo'), textInfo)}

+
+ + +
+ )} + zIndex={zIndex} + display={display} + /> + )); }, [lo]); useEffect(() => { @@ -463,18 +498,14 @@ export default function Chat() {

{locale('student')}({listState.studentsList?.length})

-

{ - openWindow(allWindow.all, IdPrompt, { - studentsList: listState.studentsList, - type: '+' - }); - }}>+

-

{ - openWindow(allWindow.all, IdPrompt, { - studentsList: listState.studentsList, - type: '-' - }); - }}>-

+ {(['+', '-'] as ('+' | '-')[]).map(v => ( +

{ + openWindow(allWindow.all, IdPrompt, { + studentsList: listState.studentsList, + type: v, + }); + }}>{v}

+ ))}
@@ -487,17 +518,37 @@ export default function Chat() { v + 1} - components={v => { + func={i => i + 1} + components={i => { if (!listState.studentsJson || !listState.studentsList) return; - const id = listState.studentsList[v]; + const id = listState.studentsList[i]; return ( setListState({ student: id })} select={listState.student === id} + onContentMenu={e => { + const info = getStudentInfo(listState.studentsJson?.data as studentsJson, id); + setContentMenu({ + x: e.clientX, + y: e.clientY, + content: [ + { type: 'title', text: `${info.schale?.Name} id: ${id}` }, + { type: 'separator', color: '#ccc', height: 1 }, + { + type: 'text', text: locale('delete'), onClick() { + openWindow(allWindow.all, IdConfirm, { + student: id, + textInfo: info.schale?.Name as string, + studentsList: listState.studentsList, + }); + } + }, + ], + display: true, + }); + }} /> ); }} diff --git a/styles/globals.scss b/styles/globals.scss index 6b647a5..ca02585 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -187,6 +187,11 @@ div.window { border-radius: 5px; margin: 5px; height: 25px; + + &.cancel { + background-color: #bbb; + color: #fff; + } } p { @@ -212,13 +217,14 @@ div.window { div#contentMenu { backdrop-filter: blur(30px); position: absolute; - width: 60px; + width: max-content; + min-width: 60px; padding: 5px; border-radius: 5px; box-shadow: rgb(0 0 0 / 30%) 0 0 20px; z-index: 10000; - > div { + > div.text { padding: 1px; border-radius: 5px; transition-duration: 0.2s; @@ -233,4 +239,14 @@ div#contentMenu { font-size: 14px; } } + + > div.separator { + margin: 5px; + } + + > p { + user-select: none; + margin: 0 5px; + font-size: 14px; + } }