feat: setting: add Load Default Settings button; home: display loaded folder info (#100)

* fix: resolve all prompt errors

* feat(home): display loaded folder info

* feat(setting): add `Load Default Settings` button
This commit is contained in:
j2rong4cn
2025-07-14 16:38:25 +08:00
committed by GitHub
parent bdc705b1e2
commit 037e103ab5
27 changed files with 186 additions and 149 deletions

View File

@@ -19,7 +19,7 @@ import { MustUser } from "./MustUser"
import "./index.css" import "./index.css"
import { useI18n } from "@solid-primitives/i18n" import { useI18n } from "@solid-primitives/i18n"
import { initialLang, langMap, loadedLangs } from "./i18n" import { initialLang, langMap, loadedLangs } from "./i18n"
import { Resp } from "~/types" import { PEmptyResp, PResp, Resp } from "~/types"
import { setArchiveExtensions } from "~/store/archive" import { setArchiveExtensions } from "~/store/archive"
const Home = lazy(() => import("~/pages/home/Layout")) const Home = lazy(() => import("~/pages/home/Layout"))

View File

@@ -44,7 +44,7 @@ export const FullLoading = (props: {
export const MaybeLoading = (props: { export const MaybeLoading = (props: {
children?: JSXElement children?: JSXElement
loading: boolean loading?: boolean
}) => { }) => {
return ( return (
<Show when={!props.loading} fallback={<FullLoading />}> <Show when={!props.loading} fallback={<FullLoading />}>

View File

@@ -31,6 +31,7 @@ const [isTocDisabled, setTocDisabled] = createStorageSignal(
export { isTocVisible, setTocDisabled } export { isTocVisible, setTocDisabled }
type PluginType = typeof remarkGfm | typeof rehypeRaw
function MarkdownToc(props: { function MarkdownToc(props: {
disabled?: boolean disabled?: boolean
markdownRef: HTMLDivElement markdownRef: HTMLDivElement
@@ -214,10 +215,10 @@ export function Markdown(props: {
}) })
return content return content
}) })
const [remarkPlugins, setRemarkPlugins] = createSignal<Function[]>([ const [remarkPlugins, setRemarkPlugins] = createSignal<PluginType[]>([
remarkGfm, remarkGfm,
]) ])
const [rehypePlugins, setRehypePlugins] = createSignal<Function[]>([ const [rehypePlugins, setRehypePlugins] = createSignal<PluginType[]>([
rehypeRaw, rehypeRaw,
]) ])
createEffect( createEffect(
@@ -228,8 +229,8 @@ export function Markdown(props: {
const { default: reMarkMath } = await import("remark-math") const { default: reMarkMath } = await import("remark-math")
const { default: rehypeKatex } = await import("rehype-katex") const { default: rehypeKatex } = await import("rehype-katex")
insertKatexCSS() insertKatexCSS()
setRemarkPlugins([...remarkPlugins(), reMarkMath]) setRemarkPlugins([...remarkPlugins(), reMarkMath as PluginType])
setRehypePlugins([...rehypePlugins(), rehypeKatex]) setRehypePlugins([...rehypePlugins(), rehypeKatex as PluginType])
} }
insertMermaidJS() insertMermaidJS()
setTimeout(() => { setTimeout(() => {

View File

@@ -92,7 +92,7 @@ export const ModalInput = (props: ModalInputProps) => {
initialFocus="#modal-input" initialFocus="#modal-input"
> >
<ModalOverlay /> <ModalOverlay />
<ModalContent onDrop={(e) => props.onDrop?.(e, setValue)}> <ModalContent onDrop={(e: DragEvent) => props.onDrop?.(e, setValue)}>
{/* <ModalCloseButton /> */} {/* <ModalCloseButton /> */}
<ModalHeader>{t(props.title)}</ModalHeader> <ModalHeader>{t(props.title)}</ModalHeader>
<ModalBody> <ModalBody>

View File

@@ -5,17 +5,14 @@ export const useLoading = <T>(
p: (...arg: any[]) => Promise<T>, p: (...arg: any[]) => Promise<T>,
fetch?: boolean, fetch?: boolean,
t?: boolean, // initial loading true t?: boolean, // initial loading true
): [ ): [Accessor<typeof t>, typeof p] => {
Accessor<boolean>, const [loading, setLoading] = createSignal(t)
(...arg: any[]) => Promise<unknown extends T ? any : T>,
] => {
const [loading, setLoading] = createSignal<boolean>(t ?? false)
return [ return [
loading, loading,
async (...arg: any[]) => { async (...arg: any[]) => {
setLoading(true) setLoading(true)
const data = await p(...arg) const data = await p(...arg)
if (!fetch || (data as unknown as EmptyResp).code !== 401) { if (!fetch || (data as EmptyResp).code !== 401) {
// why? // why?
// because if setLoading(false) here will rerender before navigate // because if setLoading(false) here will rerender before navigate
// maybe cause some bugs // maybe cause some bugs
@@ -29,10 +26,7 @@ export const useLoading = <T>(
export const useFetch = <T>( export const useFetch = <T>(
p: (...arg: any[]) => PResp<T>, p: (...arg: any[]) => PResp<T>,
loading?: boolean, loading?: boolean,
): [ ): [Accessor<typeof loading>, typeof p] => {
Accessor<boolean>,
(...arg: Parameters<typeof p>) => PResp<unknown extends T ? any : T>,
] => {
return useLoading(p, true, loading) return useLoading(p, true, loading)
} }
@@ -40,13 +34,13 @@ const useListLoading = <T, K>(
p: (key: K, ...arg: any[]) => Promise<T>, p: (key: K, ...arg: any[]) => Promise<T>,
fetch?: boolean, fetch?: boolean,
initial?: K, initial?: K,
): [Accessor<K | undefined>, (key: K, ...arg: any[]) => Promise<any>] => { ): [Accessor<typeof initial>, typeof p] => {
const [loading, setLoading] = createSignal<K | undefined>(initial) const [loading, setLoading] = createSignal(initial)
return [ return [
loading, loading,
async (key: K, ...arg: any[]) => { async (key: K, ...arg: any[]) => {
setLoading(() => key) setLoading(() => key)
const data: unknown = await p(key, ...arg) const data = await p(key, ...arg)
if (!fetch || (data as EmptyResp).code !== 401) { if (!fetch || (data as EmptyResp).code !== 401) {
setLoading(undefined) setLoading(undefined)
} }
@@ -58,6 +52,6 @@ const useListLoading = <T, K>(
export const useListFetch = <T, K>( export const useListFetch = <T, K>(
p: (key: K, ...arg: any[]) => PResp<T>, p: (key: K, ...arg: any[]) => PResp<T>,
initial?: K, initial?: K,
): [Accessor<K | undefined>, (key: K, ...arg: any[]) => Promise<any>] => { ): [Accessor<typeof initial>, typeof p] => {
return useListLoading(p, true, initial) return useListLoading(p, true, initial)
} }

View File

@@ -1,5 +1,6 @@
{ {
"obj": { "obj": {
"selected": "Selected",
"name": "Name", "name": "Name",
"size": "Size", "size": "Size",
"modified": "Modified" "modified": "Modified"

View File

@@ -124,5 +124,7 @@
"version": "Version", "version": "Version",
"video_autoplay": "Video autoplay", "video_autoplay": "Video autoplay",
"video_types": "Video types", "video_types": "Video types",
"load_default_setting": "Load default settings",
"load_default_setting_success": "Loaded successfully, Not saved",
"webauthn_login_enabled": "Webauthn login enabled" "webauthn_login_enabled": "Webauthn login enabled"
} }

View File

@@ -11,7 +11,7 @@ const Index = () => {
useTitle(getSetting("site_title")) useTitle(getSetting("site_title"))
const announcement = getSetting("announcement") const announcement = getSetting("announcement")
if (announcement) { if (announcement) {
notify.render(() => <Markdown children={announcement} />) notify.render(<Markdown children={announcement} />)
} }
return ( return (
<> <>

View File

@@ -4,11 +4,13 @@ import {
BreadcrumbLink, BreadcrumbLink,
BreadcrumbProps, BreadcrumbProps,
BreadcrumbSeparator, BreadcrumbSeparator,
HStack,
Text,
} from "@hope-ui/solid" } from "@hope-ui/solid"
import { Link } from "@solidjs/router" import { Link } from "@solidjs/router"
import { createMemo, For, Show } from "solid-js" import { createMemo, For, Show } from "solid-js"
import { usePath, useRouter, useT } from "~/hooks" import { usePath, useRouter, useT } from "~/hooks"
import { getSetting, local } from "~/store" import { getSetting, local, objStore, State } from "~/store"
import { encodePath, hoverColor, joinBase } from "~/utils" import { encodePath, hoverColor, joinBase } from "~/utils"
export const Nav = () => { export const Nav = () => {
@@ -17,6 +19,38 @@ export const Nav = () => {
const t = useT() const t = useT()
const { setPathAs } = usePath() const { setPathAs } = usePath()
const folderInfo = createMemo(() => {
const { folder, file } = objStore.objs.reduce(
(acc, item) => {
if (item.is_dir) {
acc.folder++
} else {
acc.file++
}
return acc
},
{ folder: 0, file: 0 },
)
const parts: string[] = []
if (folder) parts.push(`${t("home.search.scopes.folder")}:${folder}`)
if (file) parts.push(`${t("home.search.scopes.file")}:${file}`)
return parts.join(" ")
})
const selectInfo = createMemo(() => {
const { selected } = objStore.objs.reduce(
(acc, item) => {
if (item.selected) acc.selected++
return acc
},
{ selected: 0 },
)
if (!selected) {
return ""
}
return ` ${t("home.obj.selected")}:${selected}`
})
const stickyProps = createMemo<BreadcrumbProps>(() => { const stickyProps = createMemo<BreadcrumbProps>(() => {
const mask: BreadcrumbProps = { const mask: BreadcrumbProps = {
_after: { _after: {
@@ -48,45 +82,58 @@ export const Nav = () => {
}) })
return ( return (
<Breadcrumb {...stickyProps} background="$background" class="nav" w="$full"> <HStack background="$background" class="nav" w="$full">
<For each={paths()}> <Breadcrumb {...stickyProps} w="$full">
{(name, i) => { <For each={paths()}>
const isLast = createMemo(() => i() === paths().length - 1) {(name, i) => {
const path = paths() const isLast = createMemo(() => i() === paths().length - 1)
.slice(0, i() + 1) const path = paths()
.join("/") .slice(0, i() + 1)
const href = encodePath(path) .join("/")
let text = () => name const href = encodePath(path)
if (text() === "") { let text = () => name
text = () => getSetting("home_icon") + t("manage.sidemenu.home") if (text() === "") {
} text = () => getSetting("home_icon") + t("manage.sidemenu.home")
return ( }
<BreadcrumbItem class="nav-item"> return (
<BreadcrumbLink <BreadcrumbItem class="nav-item">
class="nav-link" <BreadcrumbLink
css={{ class="nav-link"
wordBreak: "break-all", css={{
}} wordBreak: "break-all",
color="unset" }}
_hover={{ bgColor: hoverColor(), color: "unset" }} color="unset"
_active={{ transform: "scale(.95)", transition: "0.1s" }} _hover={{ bgColor: hoverColor(), color: "unset" }}
cursor="pointer" _active={{ transform: "scale(.95)", transition: "0.1s" }}
p="$1" cursor="pointer"
rounded="$lg" p="$1"
currentPage={isLast()} rounded="$lg"
as={isLast() ? undefined : Link} currentPage={isLast()}
href={joinBase(href)} as={isLast() ? undefined : Link}
onMouseEnter={() => setPathAs(path)} href={joinBase(href)}
> onMouseEnter={() => setPathAs(path)}
{text} >
</BreadcrumbLink> {text()}
<Show when={!isLast()}> </BreadcrumbLink>
<BreadcrumbSeparator class="nav-separator" /> <Show when={!isLast()}>
</Show> <BreadcrumbSeparator class="nav-separator" />
</BreadcrumbItem> </Show>
) </BreadcrumbItem>
}} )
</For> }}
</Breadcrumb> </For>
</Breadcrumb>
<Show when={objStore.state == State.Folder}>
<Text
css={{
whiteSpace: "nowrap",
}}
p="$1"
>
{folderInfo()}
{selectInfo()}
</Text>
</Show>
</HStack>
) )
} }

View File

@@ -1,4 +1,5 @@
import { Box } from "@hope-ui/solid" import { Box } from "@hope-ui/solid"
// @ts-ignore
import * as AsciinemaPlayer from "asciinema-player" import * as AsciinemaPlayer from "asciinema-player"
import "asciinema-player/dist/bundle/asciinema-player.css" import "asciinema-player/dist/bundle/asciinema-player.css"
import { onMount } from "solid-js" import { onMount } from "solid-js"

View File

@@ -30,7 +30,7 @@ const Preview = () => {
let libheif: any let libheif: any
let decoder: any let decoder: any
let canvas: HTMLCanvasElement | null = null let canvas: HTMLCanvasElement | undefined
onMount(() => { onMount(() => {
window.addEventListener("keydown", onKeydown) window.addEventListener("keydown", onKeydown)
@@ -74,21 +74,20 @@ const Preview = () => {
} }
// 加载脚本 // 加载脚本
const loadScript = (src: string, id: string) => { const loadScript = (src: string, id: string) =>
return new Promise<void>((resolve, reject) => { new Promise<void>((resolve, reject) => {
const script = document.createElement("script") const script = document.createElement("script")
script.src = src script.src = src
script.id = id script.id = id
script.onload = () => resolve() script.onload = () => resolve()
script.onerror = () => reject(new Error(`脚本加载失败: ${src}`)) script.onerror = () => reject(`脚本加载失败: ${src}`)
document.head.appendChild(script) document.head.appendChild(script)
}) })
}
// 获取WASM文件 // 获取WASM文件
const fetchWasm = async (url: string) => { const fetchWasm = async (url: string) => {
const response = await fetch(url) const response = await fetch(url)
if (!response.ok) throw new Error(`WASM加载失败: ${url}`) if (!response.ok) throw `WASM加载失败: ${url}`
return await response.arrayBuffer() return await response.arrayBuffer()
} }
@@ -100,13 +99,13 @@ const Preview = () => {
// 获取HEIC文件 // 获取HEIC文件
const response = await fetch(url) const response = await fetch(url)
if (!response.ok) throw new Error("文件获取失败") if (!response.ok) throw "文件获取失败"
const buffer = await response.arrayBuffer() const buffer = await response.arrayBuffer()
// 解码HEIC文件 // 解码HEIC文件
const images = decoder.decode(buffer) const images = decoder.decode(buffer)
if (!images || images.length === 0) { if (!images || images.length === 0) {
throw new Error("没有可解码的图像") throw "没有可解码的图像"
} }
// 显示第一张图像 // 显示第一张图像

View File

@@ -166,7 +166,7 @@ export const VideoBox = (props: {
whiteSpace: "nowrap", whiteSpace: "nowrap",
}} }}
defaultChecked={autoNext === "true"} defaultChecked={autoNext === "true"}
onChange={(e) => { onChange={(e: { currentTarget: HTMLInputElement }) => {
props.onAutoNextChange(e.currentTarget.checked) props.onAutoNextChange(e.currentTarget.checked)
localStorage.setItem( localStorage.setItem(
"video_auto_next", "video_auto_next",

View File

@@ -58,13 +58,7 @@ function LocalSettingEdit(props: LocalSetting) {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectListbox> <SelectListbox>
<For <For each={props.options}>
each={
typeof props.options === "function"
? props.options()
: props.options
}
>
{(item) => ( {(item) => (
<SelectOption value={item}> <SelectOption value={item}>
<SelectOptionText> <SelectOptionText>
@@ -81,7 +75,7 @@ function LocalSettingEdit(props: LocalSetting) {
<Match when={props.type === "boolean"}> <Match when={props.type === "boolean"}>
<HopeSwitch <HopeSwitch
defaultChecked={local[props.key] === "true"} defaultChecked={local[props.key] === "true"}
onChange={(e) => { onChange={(e: { currentTarget: HTMLInputElement }) => {
setLocal(props.key, e.currentTarget.checked.toString()) setLocal(props.key, e.currentTarget.checked.toString())
}} }}
/> />

View File

@@ -36,11 +36,7 @@ const CornerBottom = (props: Props) => {
</linearGradient> </linearGradient>
</defs> </defs>
<g opacity="1"> <g opacity="1">
<use <use href="#path-2" fill="url(#linearGradient-3)" fill-opacity="1" />
xlink:href="#path-2"
fill="url(#linearGradient-3)"
fill-opacity="1"
/>
</g> </g>
</svg> </svg>
) )

View File

@@ -36,11 +36,7 @@ const CornerTop = (props: Props) => {
</linearGradient> </linearGradient>
</defs> </defs>
<g opacity="1"> <g opacity="1">
<use <use href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1" />
xlink:href="#path-1"
fill="url(#linearGradient-2)"
fill-opacity="1"
/>
</g> </g>
</svg> </svg>
) )

View File

@@ -12,7 +12,7 @@ import { useT } from "~/hooks"
export interface DeletePopoverProps { export interface DeletePopoverProps {
name: string name: string
loading: boolean loading?: boolean
onClick: () => void onClick: () => void
} }
export const DeletePopover = (props: DeletePopoverProps) => { export const DeletePopover = (props: DeletePopoverProps) => {

View File

@@ -105,7 +105,7 @@ const AddOrEdit = () => {
const initEdit = async () => { const initEdit = async () => {
const resp = await loadMeta() const resp = await loadMeta()
handleResp(resp, setMeta) handleResp<Meta>(resp, setMeta)
} }
if (id) { if (id) {
initEdit() initEdit()
@@ -154,14 +154,12 @@ const AddOrEdit = () => {
// @ts-ignore // @ts-ignore
<Item <Item
name={item.name} name={item.name}
type={item.type as "string" | "bool" | "text"} type={item.type}
value={meta[item.name as keyof Meta] as string | boolean} value={meta[item.name]}
onChange={(val: any): void => onChange={(val: any): void => setMeta(item.name, val)}
setMeta(item.name as keyof Meta, val) sub={meta[item.sub]}
} onSub={(val): void => setMeta(item.sub, val)}
sub={meta[item.sub] as boolean} help={(item as { help: boolean }).help}
onSub={(val: boolean): void => setMeta(item.sub, val)}
help={item.help}
/> />
) )
}} }}

View File

@@ -19,7 +19,7 @@ import {
useT, useT,
} from "~/hooks" } from "~/hooks"
import { handleResp, notify, r } from "~/utils" import { handleResp, notify, r } from "~/utils"
import { Meta, PageResp } from "~/types" import { Meta, PEmptyResp, PPageResp } from "~/types"
import { DeletePopover } from "../common/DeletePopover" import { DeletePopover } from "../common/DeletePopover"
import { Wether } from "~/components" import { Wether } from "~/components"
@@ -27,16 +27,18 @@ const Metas = () => {
const t = useT() const t = useT()
useManageTitle("manage.sidemenu.metas") useManageTitle("manage.sidemenu.metas")
const { to } = useRouter() const { to } = useRouter()
const [getMetasLoading, getMetas] = useFetch(() => r.get("/admin/meta/list")) const [getMetasLoading, getMetas] = useFetch(
(): PPageResp<Meta> => r.get("/admin/meta/list"),
)
const [metas, setMetas] = createSignal<Meta[]>([]) const [metas, setMetas] = createSignal<Meta[]>([])
const refresh = async () => { const refresh = async () => {
const resp: PageResp<Meta> = await getMetas() const resp = await getMetas()
handleResp(resp, (data) => setMetas(data.content)) handleResp(resp, (data) => setMetas(data.content))
} }
refresh() refresh()
const [deleting, deleteMeta] = useListFetch((id: number) => const [deleting, deleteMeta] = useListFetch(
r.post(`/admin/meta/delete?id=${id}`), (id: number): PEmptyResp => r.post(`/admin/meta/delete?id=${id}`),
) )
return ( return (
<VStack spacing="$2" alignItems="start" w="$full"> <VStack spacing="$2" alignItems="start" w="$full">

View File

@@ -21,18 +21,22 @@ const CommonSettings = (props: CommonSettingsProps) => {
const [settings, setSettings] = createStore<SettingItem[]>([]) const [settings, setSettings] = createStore<SettingItem[]>([])
const refresh = async () => { const refresh = async () => {
const resp = await getSettings() const resp = await getSettings()
handleResp(resp, setSettings) handleResp<SettingItem[]>(resp, setSettings)
} }
refresh() refresh()
const [saveLoading, saveSettings] = useFetch( const [saveLoading, saveSettings] = useFetch(
(): PEmptyResp => r.post("/admin/setting/save", getTarget(settings)), (): PEmptyResp => r.post("/admin/setting/save", getTarget(settings)),
) )
const [defaultLoading, defaultSettings] = useFetch(
(): PResp<SettingItem[]> =>
r.post(`/admin/setting/default?group=${props.group}`),
)
const [loading, setLoading] = createSignal(false) const [loading, setLoading] = createSignal(false)
return ( return (
<VStack w="$full" alignItems="start" spacing="$2"> <VStack w="$full" alignItems="start" spacing="$2">
<ResponsiveGrid> <ResponsiveGrid>
<Index each={settings}> <Index each={settings}>
{(item, _) => ( {(item) => (
<Item <Item
{...item()} {...item()}
onChange={(val) => { onChange={(val) => {
@@ -57,7 +61,7 @@ const CommonSettings = (props: CommonSettingsProps) => {
<Button <Button
colorScheme="accent" colorScheme="accent"
onClick={refresh} onClick={refresh}
loading={settingsLoading() || loading()} loading={settingsLoading() || loading() || defaultLoading()}
> >
{t("global.refresh")} {t("global.refresh")}
</Button> </Button>
@@ -70,6 +74,19 @@ const CommonSettings = (props: CommonSettingsProps) => {
> >
{t("global.save")} {t("global.save")}
</Button> </Button>
<Button
colorScheme="warning"
loading={settingsLoading() || loading() || defaultLoading()}
onClick={async () => {
const resp = await defaultSettings()
handleResp(resp, (data) => {
notify.info(t("settings.load_default_setting_success"))
setSettings(data)
})
}}
>
{t("settings.load_default_setting")}
</Button>
</HStack> </HStack>
</VStack> </VStack>
) )

View File

@@ -24,7 +24,7 @@ const S3Settings = () => {
const [settings, setSettings] = createStore<SettingItem[]>([]) const [settings, setSettings] = createStore<SettingItem[]>([])
const refresh = async () => { const refresh = async () => {
const resp = await getSettings() const resp = await getSettings()
handleResp(resp, setSettings) handleResp<SettingItem[]>(resp, setSettings)
} }
refresh() refresh()
const [saveLoading, saveSettings] = useFetch( const [saveLoading, saveSettings] = useFetch(

View File

@@ -26,7 +26,6 @@ import { TiDelete } from "solid-icons/ti"
export type ItemProps = SettingItem & { export type ItemProps = SettingItem & {
onChange?: (value: string) => void onChange?: (value: string) => void
onDelete?: () => void onDelete?: () => void
// value: () => string;
hideLabel?: boolean hideLabel?: boolean
w?: string w?: string
} }
@@ -58,7 +57,6 @@ const Item = (props: ItemProps) => {
<Input <Input
type={props.type === Type.Number ? "number" : ""} type={props.type === Type.Number ? "number" : ""}
id={props.key} id={props.key}
// value={props.value()}
value={props.value} value={props.value}
onInput={(e) => props.onChange?.(e.currentTarget.value)} onInput={(e) => props.onChange?.(e.currentTarget.value)}
readOnly={props.flag === Flag.READONLY} readOnly={props.flag === Flag.READONLY}
@@ -67,11 +65,9 @@ const Item = (props: ItemProps) => {
<Match when={props.type === Type.Bool}> <Match when={props.type === Type.Bool}>
<HopeSwitch <HopeSwitch
id={props.key} id={props.key}
defaultChecked={props.value === "true"} checked={props.value === "true"}
// checked={props.value() === "true"} onChange={(e: { currentTarget: HTMLInputElement }) =>
onChange={(e: any) => props.onChange?.(e.currentTarget?.checked ? "true" : "false")
// props.onChange?.(props.value() === "true" ? "false" : "true")
props.onChange?.(e.currentTarget.checked ? "true" : "false")
} }
readOnly={props.flag === Flag.READONLY} readOnly={props.flag === Flag.READONLY}
/> />
@@ -80,7 +76,6 @@ const Item = (props: ItemProps) => {
<Textarea <Textarea
id={props.key} id={props.key}
value={props.value} value={props.value}
// value={props.value()}
onChange={(e) => props.onChange?.(e.currentTarget.value)} onChange={(e) => props.onChange?.(e.currentTarget.value)}
readOnly={props.flag === Flag.READONLY} readOnly={props.flag === Flag.READONLY}
/> />
@@ -88,8 +83,7 @@ const Item = (props: ItemProps) => {
<Match when={props.type === Type.Select}> <Match when={props.type === Type.Select}>
<Select <Select
id={props.key} id={props.key}
defaultValue={props.value} value={props.value}
// value={props.value()}
onChange={(e) => props.onChange?.(e)} onChange={(e) => props.onChange?.(e)}
readOnly={props.flag === Flag.READONLY} readOnly={props.flag === Flag.READONLY}
> >

View File

@@ -130,8 +130,10 @@ const Storages = () => {
</Show> </Show>
<HopeSwitch <HopeSwitch
checked={layout() === "table"} checked={layout() === "table"}
onChange={(e) => { onChange={(e: Event) => {
setLayout(e.currentTarget.checked ? "table" : "grid") setLayout(
(e.currentTarget as HTMLInputElement).checked ? "table" : "grid",
)
}} }}
> >
{t("storages.other.table_layout")} {t("storages.other.table_layout")}

View File

@@ -14,7 +14,7 @@ import { handleResp, notify, r } from "~/utils"
import { PEmptyResp, PResp, User, UserMethods, UserPermissions } from "~/types" import { PEmptyResp, PResp, User, UserMethods, UserPermissions } from "~/types"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { For, Show } from "solid-js" import { For, Show } from "solid-js"
import { me, setMe } from "~/store" import { Me, me, setMe } from "~/store"
import { PublicKeys } from "./PublicKeys" import { PublicKeys } from "./PublicKeys"
const Permission = (props: { const Permission = (props: {
@@ -63,7 +63,7 @@ const AddOrEdit = () => {
const initEdit = async () => { const initEdit = async () => {
const resp = await loadUser() const resp = await loadUser()
handleResp(resp, setUser) handleResp<User>(resp, setUser)
} }
if (id) { if (id) {
initEdit() initEdit()
@@ -153,7 +153,7 @@ const AddOrEdit = () => {
handleResp(resp, async () => { handleResp(resp, async () => {
notify.success(t("global.save_success")) notify.success(t("global.save_success"))
if (user.username === me().username) if (user.username === me().username)
handleResp(await r.get("/me"), setMe) handleResp(await (r.get("/me") as PResp<Me>), setMe)
back() back()
}) })
}} }}

View File

@@ -58,6 +58,7 @@ export const initialLocalSettings = [
default: "direct", default: "direct",
type: "select", type: "select",
options: ["direct", "dblclick", "disable_while_checked"], options: ["direct", "dblclick", "disable_while_checked"],
hidden: false,
}, },
] ]
export type LocalSetting = (typeof initialLocalSettings)[number] export type LocalSetting = (typeof initialLocalSettings)[number]

View File

@@ -16,39 +16,25 @@ export enum State {
File, // File state File, // File state
NeedPassword, NeedPassword,
} }
const initialObjStore = {
const [objStore, setObjStore] = createStore<{
obj: Obj
raw_url: string
related: Obj[]
objs: StoreObj[]
total: number
write?: boolean
readme: string
header: string
provider: string
// pageIndex: number;
// pageSize: number;
state: State
err: string
}>({
obj: {} as Obj, obj: {} as Obj,
raw_url: "", raw_url: "",
related: [], related: [] as Obj[],
objs: [], objs: [] as StoreObj[],
total: 0, total: 0,
readme: "", readme: "",
header: "", header: "",
provider: "", provider: "",
// pageIndex: 1,
// pageSize: 50,
state: State.Initial, state: State.Initial,
err: "", err: "",
}) }
const [objStore, setObjStore] = createStore<
typeof initialObjStore & {
write?: boolean
}
>(initialObjStore)
const setObjs = (objs: Obj[]) => { const setObjs = (objs: Obj[]) => {
lastChecked.start = -1 lastChecked.start = -1
@@ -96,7 +82,6 @@ export type OrderBy = "name" | "size" | "modified"
export const sortObjs = (orderBy: OrderBy, reverse?: boolean) => { export const sortObjs = (orderBy: OrderBy, reverse?: boolean) => {
log("sort:", orderBy, reverse) log("sort:", orderBy, reverse)
naturalSort.insensitive = true
setObjStore( setObjStore(
"objs", "objs",
produce((objs) => produce((objs) =>

View File

@@ -26,7 +26,13 @@ const notify = {
padding: "$3", padding: "$3",
}} }}
> >
<div style={{ flexGrow: 1, display: "flex", alignItems: "center" }}> <div
style={{
"flex-grow": 1,
display: "flex",
"align-items": "center",
}}
>
<div style={{ margin: "auto" }}>{element}</div> <div style={{ margin: "auto" }}>{element}</div>
</div> </div>
<div style={{ display: "inline-block", padding: "5px" }}> <div style={{ display: "inline-block", padding: "5px" }}>

View File

@@ -15,6 +15,7 @@
"~/*": ["./src/*"] "~/*": ["./src/*"]
// "@solidjs/router": ["./solid-router/src/index.tsx"] // "@solidjs/router": ["./solid-router/src/index.tsx"]
}, },
"skipLibCheck": true,
"resolveJsonModule": true "resolveJsonModule": true
} }
} }