mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-02-04 12:40:08 +00:00
optimize fm (#82)
* optimize fm * chore: auto-fix linting and formatting issues * fix: type * chore: auto-fix linting and formatting issues --------- Co-authored-by: uubulb <uubulb@users.noreply.github.com> Co-authored-by: naiba <hi@nai.ba> Co-authored-by: naiba <naiba@users.noreply.github.com>
This commit is contained in:
@@ -25,7 +25,7 @@ let lastestRefreshTokenAt = 0
|
||||
|
||||
export async function fetcher<T>(method: FetcherMethod, path: string, data?: any): Promise<T> {
|
||||
let response
|
||||
if (method === FetcherMethod.GET || method === FetcherMethod.DELETE) {
|
||||
if (method === FetcherMethod.GET || method === FetcherMethod.DELETE) {
|
||||
response = await fetch(buildUrl(path, data), {
|
||||
method: "GET",
|
||||
})
|
||||
|
||||
@@ -7,27 +7,45 @@ export enum Oauth2RequestType {
|
||||
BIND = 2,
|
||||
}
|
||||
|
||||
export const getOauth2RedirectURL = async (provider: string, rType: Oauth2RequestType): Promise<ModelOauth2LoginResponse> => {
|
||||
export const getOauth2RedirectURL = async (
|
||||
provider: string,
|
||||
rType: Oauth2RequestType,
|
||||
): Promise<ModelOauth2LoginResponse> => {
|
||||
const sType = "type"
|
||||
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.GET, `/api/v1/oauth2/${provider}`, {
|
||||
"type": rType,
|
||||
sType: rType,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const bindOauth2 = async (provider: string, state: string, code: string): Promise<ModelOauth2LoginResponse> => {
|
||||
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/bind`, {
|
||||
"state": state,
|
||||
"code": code,
|
||||
})
|
||||
export const bindOauth2 = async (
|
||||
provider: string,
|
||||
state: string,
|
||||
code: string,
|
||||
): Promise<ModelOauth2LoginResponse> => {
|
||||
return fetcher<ModelOauth2LoginResponse>(
|
||||
FetcherMethod.POST,
|
||||
`/api/v1/oauth2/${provider}/bind`,
|
||||
{
|
||||
state: state,
|
||||
code: code,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export const unbindOauth2 = async (provider: string): Promise<ModelOauth2LoginResponse> => {
|
||||
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/unbind`)
|
||||
return fetcher<ModelOauth2LoginResponse>(
|
||||
FetcherMethod.POST,
|
||||
`/api/v1/oauth2/${provider}/unbind`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export const oauth2callback = async (provider: string, state: string, code: string): Promise<void> => {
|
||||
export const oauth2callback = async (
|
||||
provider: string,
|
||||
state: string,
|
||||
code: string,
|
||||
): Promise<void> => {
|
||||
return fetcher<void>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/callback`, {
|
||||
state, code
|
||||
state,
|
||||
code,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import { HTMLAttributes, useEffect, useRef, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { Button } from "./ui/button"
|
||||
import { TableCell, TableRow } from "./ui/table"
|
||||
import { Filepath } from "./xui/filepath"
|
||||
import { IconButton } from "./xui/icon-button"
|
||||
@@ -102,7 +103,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
header: () => <span>{t("Actions")}</span>,
|
||||
id: "download",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
return row.original.type == 0 ? (
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
icon="download"
|
||||
@@ -111,6 +112,8 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
downloadFile(row.original.name)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button size="icon" variant="ghost" className="pointer-events-none" />
|
||||
)
|
||||
},
|
||||
},
|
||||
@@ -157,42 +160,6 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
}
|
||||
}
|
||||
|
||||
worker.onmessage = async (event: MessageEvent<FMWorkerData>) => {
|
||||
switch (event.data.type) {
|
||||
case FMWorkerOpcode.Error: {
|
||||
console.error("Error from worker", event.data.error)
|
||||
break
|
||||
}
|
||||
case FMWorkerOpcode.Progress: {
|
||||
handleReady.current = true
|
||||
break
|
||||
}
|
||||
case FMWorkerOpcode.Result: {
|
||||
handleReady.current = false
|
||||
|
||||
if (event.data.blob && event.data.fileName) {
|
||||
const url = URL.createObjectURL(event.data.blob)
|
||||
const anchor = document.createElement("a")
|
||||
anchor.href = url
|
||||
anchor.download = event.data.fileName
|
||||
anchor.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
firstChunk.current = true
|
||||
if (dOpen) setdOpen(false)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [currentPath, setPath] = useState("")
|
||||
useEffect(() => {
|
||||
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
||||
listFile()
|
||||
}
|
||||
}, [wsRef.current, currentPath])
|
||||
|
||||
useEffect(() => {
|
||||
const ws = new WebSocket(wsUrl)
|
||||
wsRef.current = ws
|
||||
@@ -209,27 +176,27 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
description: t("Results.UnExpectedError"),
|
||||
})
|
||||
}
|
||||
ws.onmessage = async (e) => {
|
||||
ws.onmessage = async (e: MessageEvent<ArrayBufferLike>) => {
|
||||
try {
|
||||
const buf: ArrayBufferLike = e.data
|
||||
const identifier = new Uint8Array(e.data, 0, 4)
|
||||
if (arraysEqual(identifier, FMIdentifier.error)) {
|
||||
const errBytes = e.data.slice(4)
|
||||
const errMsg = new TextDecoder("utf-8").decode(errBytes)
|
||||
throw new Error(errMsg)
|
||||
}
|
||||
|
||||
if (firstChunk.current) {
|
||||
const identifier = new Uint8Array(buf, 0, 4)
|
||||
if (arraysEqual(identifier, FMIdentifier.file)) {
|
||||
worker.postMessage({
|
||||
operation: 1,
|
||||
arrayBuffer: buf,
|
||||
arrayBuffer: e.data,
|
||||
fileName: currentBasename.current,
|
||||
})
|
||||
firstChunk.current = false
|
||||
} else if (arraysEqual(identifier, FMIdentifier.fileName)) {
|
||||
const { path, fmList } = await fm.parseFMList(buf)
|
||||
const { path, fmList } = await fm.parseFMList(e.data)
|
||||
setPath(path)
|
||||
setFMEntries(fmList)
|
||||
} else if (arraysEqual(identifier, FMIdentifier.error)) {
|
||||
const errBytes = buf.slice(4)
|
||||
const errMsg = new TextDecoder("utf-8").decode(errBytes)
|
||||
throw new Error(errMsg)
|
||||
} else if (arraysEqual(identifier, FMIdentifier.complete)) {
|
||||
// Upload completed
|
||||
if (uOpen) setuOpen(false)
|
||||
@@ -241,7 +208,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
await waitForHandleReady()
|
||||
worker.postMessage({
|
||||
operation: 2,
|
||||
arrayBuffer: buf,
|
||||
arrayBuffer: e.data,
|
||||
fileName: currentBasename.current,
|
||||
})
|
||||
}
|
||||
@@ -250,12 +217,61 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
toast("FM" + " " + t("Error"), {
|
||||
description: t("Results.UnExpectedError"),
|
||||
})
|
||||
if (dOpen) setdOpen(false)
|
||||
if (uOpen) setuOpen(false)
|
||||
setdOpen(false)
|
||||
setuOpen(false)
|
||||
}
|
||||
}
|
||||
}, [wsUrl])
|
||||
|
||||
useEffect(() => {
|
||||
worker.onmessage = async (event: MessageEvent<FMWorkerData>) => {
|
||||
switch (event.data.type) {
|
||||
case FMWorkerOpcode.Error: {
|
||||
console.error("Error from worker", event.data.error)
|
||||
break
|
||||
}
|
||||
case FMWorkerOpcode.Progress: {
|
||||
handleReady.current = true
|
||||
break
|
||||
}
|
||||
case FMWorkerOpcode.Result: {
|
||||
handleReady.current = false
|
||||
|
||||
if (event.data.blob && event.data.fileName) {
|
||||
const url = URL.createObjectURL(event.data.blob)
|
||||
const anchor = document.createElement("a")
|
||||
anchor.href = url
|
||||
anchor.download = event.data.fileName
|
||||
anchor.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
firstChunk.current = true
|
||||
if (dOpen) setdOpen(false)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleBeforeUnload = () => {
|
||||
worker.postMessage({
|
||||
operation: 3,
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", handleBeforeUnload)
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload)
|
||||
}
|
||||
}, [worker, dOpen])
|
||||
|
||||
const [currentPath, setPath] = useState("")
|
||||
useEffect(() => {
|
||||
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
||||
listFile()
|
||||
}
|
||||
}, [wsRef.current, currentPath])
|
||||
|
||||
const listFile = () => {
|
||||
const prefix = new Int8Array([FMOpcode.List])
|
||||
const pathMsg = new TextEncoder().encode(currentPath)
|
||||
@@ -317,7 +333,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
toast("FM" + " " + t("Error"), {
|
||||
description: error.message,
|
||||
})
|
||||
console.log("copy error: ", error)
|
||||
console.error("copy error: ", error)
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { useAuth } from "@/hooks/useAuth"
|
||||
import useSettings from "@/hooks/useSetting"
|
||||
import { copyToClipboard } from "@/lib/utils"
|
||||
import { ModelProfile, ModelConfig } from "@/types"
|
||||
import { ModelConfig, ModelProfile } from "@/types"
|
||||
import i18next from "i18next"
|
||||
import { Check, Clipboard } from "lucide-react"
|
||||
import { forwardRef, useState } from "react"
|
||||
|
||||
@@ -52,7 +52,7 @@ export const Filepath: React.FC<FilepathProps> = ({ path, setPath }) => {
|
||||
setPath("/")
|
||||
}}
|
||||
>
|
||||
{"/"}
|
||||
/
|
||||
</p>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { oauth2callback } from "@/api/oauth2"
|
||||
import { getProfile, login as loginRequest } from "@/api/user"
|
||||
import { AuthContextProps } from "@/types"
|
||||
import { createContext, useContext, useEffect, useMemo } from "react"
|
||||
@@ -5,13 +6,12 @@ import { useNavigate } from "react-router-dom"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { useMainStore } from "./useMainStore"
|
||||
import { oauth2callback } from "@/api/oauth2"
|
||||
|
||||
const AuthContext = createContext<AuthContextProps>({
|
||||
profile: undefined,
|
||||
login: () => { },
|
||||
loginOauth2: () => { },
|
||||
logout: () => { },
|
||||
login: () => {},
|
||||
loginOauth2: () => {},
|
||||
logout: () => {},
|
||||
})
|
||||
|
||||
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
@@ -19,7 +19,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const setProfile = useMainStore((store) => store.setProfile)
|
||||
|
||||
useEffect(() => {
|
||||
; (async () => {
|
||||
;(async () => {
|
||||
try {
|
||||
const user = await getProfile()
|
||||
user.role = user.role || 0
|
||||
|
||||
@@ -3,6 +3,8 @@ import { GithubComNezhahqNezhaModelSettingResponseModelConfig } from "@/types"
|
||||
import useSWR from "swr"
|
||||
|
||||
export default function useSetting() {
|
||||
return useSWR<GithubComNezhahqNezhaModelSettingResponseModelConfig>("/api/v1/setting", swrFetcher)
|
||||
return useSWR<GithubComNezhahqNezhaModelSettingResponseModelConfig>(
|
||||
"/api/v1/setting",
|
||||
swrFetcher,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,9 +38,8 @@ onmessage = async function (event) {
|
||||
throw new Error("accessHandle is undefined")
|
||||
}
|
||||
|
||||
const dataChunk = arrayBuffer
|
||||
accessHandle.write(dataChunk, { at: receivedLength })
|
||||
receivedLength += dataChunk.byteLength
|
||||
accessHandle.write(arrayBuffer, { at: receivedLength })
|
||||
receivedLength += arrayBuffer.byteLength
|
||||
|
||||
if (receivedLength === expectedLength) {
|
||||
accessHandle.flush()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getOauth2RedirectURL, Oauth2RequestType } from "@/api/oauth2"
|
||||
import { Oauth2RequestType, getOauth2RedirectURL } from "@/api/oauth2"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Form,
|
||||
@@ -59,7 +59,7 @@ function Login() {
|
||||
window.location.href = redirectUrl.redirect!
|
||||
} catch (error: any) {
|
||||
toast.error(error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { t } = useTranslation()
|
||||
@@ -103,9 +103,9 @@ function Login() {
|
||||
</form>
|
||||
</Form>
|
||||
<div className="mt-4">
|
||||
{settingData?.config?.oauth2_providers?.map((p: string) =>
|
||||
{settingData?.config?.oauth2_providers?.map((p: string) => (
|
||||
<Button onClick={() => loginWith(p)}>{p}</Button>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { bindOauth2, getOauth2RedirectURL, Oauth2RequestType, unbindOauth2 } from "@/api/oauth2"
|
||||
import { Oauth2RequestType, bindOauth2, getOauth2RedirectURL, unbindOauth2 } from "@/api/oauth2"
|
||||
import { getProfile } from "@/api/user"
|
||||
import { ProfileCard } from "@/components/profile"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
@@ -31,7 +31,8 @@ export default function ProfilePage() {
|
||||
getProfile().then((profile) => {
|
||||
setProfile(profile)
|
||||
})
|
||||
}).finally(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
window.history.replaceState({}, document.title, window.location.pathname)
|
||||
})
|
||||
}
|
||||
@@ -116,12 +117,20 @@ export default function ProfilePage() {
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-lg font-semibold">
|
||||
{settingData?.config?.oauth2_providers?.map((provider) => <div>
|
||||
{provider}: {profile.oauth2_bind?.[provider.toLowerCase()]} {profile.oauth2_bind?.[provider.toLowerCase()] ?
|
||||
<Button size="sm" onClick={() => unbindO2(provider)}>Unbind</Button>
|
||||
:
|
||||
<Button size="sm" onClick={() => bindO2(provider)}>Bind</Button>}
|
||||
</div>)}
|
||||
{settingData?.config?.oauth2_providers?.map((provider) => (
|
||||
<div>
|
||||
{provider}: {profile.oauth2_bind?.[provider.toLowerCase()]}{" "}
|
||||
{profile.oauth2_bind?.[provider.toLowerCase()] ? (
|
||||
<Button size="sm" onClick={() => unbindO2(provider)}>
|
||||
Unbind
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" onClick={() => bindO2(provider)}>
|
||||
Bind
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -67,21 +67,21 @@ export default function SettingsPage() {
|
||||
resolver: zodResolver(settingFormSchema),
|
||||
defaultValues: config
|
||||
? {
|
||||
...config,
|
||||
language: config?.config?.language,
|
||||
site_name: config.config?.site_name || "",
|
||||
user_template:
|
||||
config.config?.user_template ||
|
||||
Object.keys(config.frontend_templates?.filter((t) => !t.is_admin) || {})[0] ||
|
||||
"user-dist",
|
||||
}
|
||||
...config,
|
||||
language: config?.config?.language,
|
||||
site_name: config.config?.site_name || "",
|
||||
user_template:
|
||||
config.config?.user_template ||
|
||||
Object.keys(config.frontend_templates?.filter((t) => !t.is_admin) || {})[0] ||
|
||||
"user-dist",
|
||||
}
|
||||
: {
|
||||
ip_change_notification_group_id: 0,
|
||||
cover: 1,
|
||||
site_name: "",
|
||||
language: "",
|
||||
user_template: "user-dist",
|
||||
},
|
||||
ip_change_notification_group_id: 0,
|
||||
cover: 1,
|
||||
site_name: "",
|
||||
language: "",
|
||||
user_template: "user-dist",
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
},
|
||||
@@ -229,15 +229,15 @@ export default function SettingsPage() {
|
||||
{!config?.frontend_templates?.find(
|
||||
(t) => t.path === field.value,
|
||||
)?.is_official && (
|
||||
<div className="mt-2 text-sm text-yellow-700 dark:text-yellow-200 bg-yellow-100 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 rounded-md p-2">
|
||||
<div className="font-medium text-lg mb-1">
|
||||
{t("CommunityThemeWarning")}
|
||||
</div>
|
||||
<div className="text-yellow-700 dark:text-yellow-200">
|
||||
{t("CommunityThemeDescription")}
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-yellow-700 dark:text-yellow-200 bg-yellow-100 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 rounded-md p-2">
|
||||
<div className="font-medium text-lg mb-1">
|
||||
{t("CommunityThemeWarning")}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-yellow-700 dark:text-yellow-200">
|
||||
{t("CommunityThemeDescription")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export interface GithubComNezhahqNezhaModelCommonResponseAny {
|
||||
data?: any
|
||||
error?: string
|
||||
|
||||
Reference in New Issue
Block a user