mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-09-20 02:00:14 +00:00
Dashboard Redesign (#48)
* feat: add user_template setting * style: header * style: page padding * style: header * feat: header now time * style: login page * feat: nav indicator * style: button inset shadow * style: footer text size * feat: header show login_ip * fix: error toast * fix: frontend_templates setting * fix: lint * feat: pr auto format * chore: auto-fix linting and formatting issues --------- Co-authored-by: hamster1963 <hamster1963@users.noreply.github.com>
This commit is contained in:
+36
-37
@@ -1,71 +1,70 @@
|
||||
let receivedLength = 0;
|
||||
let expectedLength = 0;
|
||||
let root: FileSystemDirectoryHandle;
|
||||
let draftHandle: FileSystemFileHandle;
|
||||
let accessHandle: FileSystemSyncAccessHandle;
|
||||
let receivedLength = 0
|
||||
let expectedLength = 0
|
||||
let root: FileSystemDirectoryHandle
|
||||
let draftHandle: FileSystemFileHandle
|
||||
let accessHandle: FileSystemSyncAccessHandle
|
||||
|
||||
enum Operation {
|
||||
WriteHeader = 1,
|
||||
WriteChunks,
|
||||
DeleteFiles,
|
||||
};
|
||||
}
|
||||
|
||||
onmessage = async function (event) {
|
||||
try {
|
||||
const { operation, arrayBuffer, fileName } = event.data;
|
||||
const { operation, arrayBuffer, fileName } = event.data
|
||||
|
||||
switch (operation) {
|
||||
case Operation.WriteHeader: {
|
||||
const dataView = new DataView(arrayBuffer);
|
||||
expectedLength = Number(dataView.getBigUint64(4, false));
|
||||
receivedLength = 0;
|
||||
const dataView = new DataView(arrayBuffer)
|
||||
expectedLength = Number(dataView.getBigUint64(4, false))
|
||||
receivedLength = 0
|
||||
|
||||
// Create a new temporary file
|
||||
root = await navigator.storage.getDirectory();
|
||||
draftHandle = await root.getFileHandle(fileName, { create: true });
|
||||
accessHandle = await draftHandle.createSyncAccessHandle();
|
||||
root = await navigator.storage.getDirectory()
|
||||
draftHandle = await root.getFileHandle(fileName, { create: true })
|
||||
accessHandle = await draftHandle.createSyncAccessHandle()
|
||||
|
||||
// Inform that file handle is created
|
||||
const dataChunk = arrayBuffer.slice(12);
|
||||
receivedLength += dataChunk.byteLength;
|
||||
accessHandle.write(dataChunk, { at: 0 });
|
||||
const progress = 'got handle';
|
||||
postMessage({ type: 1, progress: progress });
|
||||
break;
|
||||
const dataChunk = arrayBuffer.slice(12)
|
||||
receivedLength += dataChunk.byteLength
|
||||
accessHandle.write(dataChunk, { at: 0 })
|
||||
const progress = "got handle"
|
||||
postMessage({ type: 1, progress: progress })
|
||||
break
|
||||
}
|
||||
case Operation.WriteChunks: {
|
||||
if (!accessHandle) {
|
||||
throw new Error('accessHandle is undefined');
|
||||
throw new Error("accessHandle is undefined")
|
||||
}
|
||||
|
||||
const dataChunk = arrayBuffer;
|
||||
accessHandle.write(dataChunk, { at: receivedLength });
|
||||
receivedLength += dataChunk.byteLength;
|
||||
const dataChunk = arrayBuffer
|
||||
accessHandle.write(dataChunk, { at: receivedLength })
|
||||
receivedLength += dataChunk.byteLength
|
||||
|
||||
if (receivedLength === expectedLength) {
|
||||
accessHandle.flush();
|
||||
accessHandle.close();
|
||||
accessHandle.flush()
|
||||
accessHandle.close()
|
||||
|
||||
const fileBlob = await draftHandle.getFile();
|
||||
const blob = new Blob([fileBlob], { type: 'application/octet-stream' });
|
||||
const fileBlob = await draftHandle.getFile()
|
||||
const blob = new Blob([fileBlob], { type: "application/octet-stream" })
|
||||
|
||||
postMessage({ type: 2, blob: blob, fileName: fileName });
|
||||
postMessage({ type: 2, blob: blob, fileName: fileName })
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
case Operation.DeleteFiles: {
|
||||
for await (const [name, handle] of root.entries()) {
|
||||
if (handle.kind === 'file') {
|
||||
await root.removeEntry(name);
|
||||
} else if (handle.kind === 'directory') {
|
||||
await root.removeEntry(name, { recursive: true });
|
||||
if (handle.kind === "file") {
|
||||
await root.removeEntry(name)
|
||||
} else if (handle.kind === "directory") {
|
||||
await root.removeEntry(name, { recursive: true })
|
||||
}
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error)
|
||||
postMessage({ type: 0, error: error.message });
|
||||
if (error instanceof Error) postMessage({ type: 0, error: error.message })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+20
-21
@@ -1,10 +1,10 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import i18n from "i18next"
|
||||
import { initReactI18next } from "react-i18next"
|
||||
|
||||
import enTranslation from "../locales/en/translation.json";
|
||||
import itTranslation from "../locales/it/translation.json";
|
||||
import zhCNTranslation from "../locales/zh-CN/translation.json";
|
||||
import zhTWTranslation from "../locales/zh-TW/translation.json";
|
||||
import enTranslation from "../locales/en/translation.json"
|
||||
import itTranslation from "../locales/it/translation.json"
|
||||
import zhCNTranslation from "../locales/zh-CN/translation.json"
|
||||
import zhTWTranslation from "../locales/zh-TW/translation.json"
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
@@ -19,24 +19,23 @@ const resources = {
|
||||
"zh-TW": {
|
||||
translation: zhTWTranslation,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const getStoredLanguage = () => {
|
||||
return localStorage.getItem("language") || "zh-CN";
|
||||
};
|
||||
return localStorage.getItem("language") || "zh-CN"
|
||||
}
|
||||
|
||||
i18n.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
lng: getStoredLanguage(), // 使用localStorage中存储的语言或默认值
|
||||
fallbackLng: "en", // 当前语言的翻译没有找到时,使用的备选语言
|
||||
interpolation: {
|
||||
escapeValue: false, // react已经安全地转义
|
||||
},
|
||||
});
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: getStoredLanguage(), // 使用localStorage中存储的语言或默认值
|
||||
fallbackLng: "en", // 当前语言的翻译没有找到时,使用的备选语言
|
||||
interpolation: {
|
||||
escapeValue: false, // react已经安全地转义
|
||||
},
|
||||
})
|
||||
|
||||
i18n.on("languageChanged", (lng) => {
|
||||
localStorage.setItem("language", lng);
|
||||
});
|
||||
localStorage.setItem("language", lng)
|
||||
})
|
||||
|
||||
export default i18n;
|
||||
export default i18n
|
||||
|
||||
+213
-211
@@ -1,262 +1,264 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { FMEntry, FMOpcode, ModelIP } from "@/types"
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import copy from "copy-to-clipboard"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { z } from "zod"
|
||||
import { FMEntry, FMOpcode, ModelIP } from "@/types"
|
||||
|
||||
import FMWorker from "./fm?worker"
|
||||
import copy from "copy-to-clipboard"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
const emptyStringToUndefined = z.literal('').transform(() => undefined);
|
||||
const emptyStringToUndefined = z.literal("").transform(() => undefined)
|
||||
|
||||
export function asOptionalField<T extends z.ZodTypeAny>(schema: T) {
|
||||
return schema.optional().or(emptyStringToUndefined);
|
||||
return schema.optional().or(emptyStringToUndefined)
|
||||
}
|
||||
|
||||
export const conv = {
|
||||
recordToStr: (rec: Record<string, boolean>) => {
|
||||
const arr: string[] = [];
|
||||
for (const key in rec) {
|
||||
arr.push(key);
|
||||
}
|
||||
recordToStr: (rec: Record<string, boolean>) => {
|
||||
const arr: string[] = []
|
||||
for (const key in rec) {
|
||||
arr.push(key)
|
||||
}
|
||||
|
||||
return arr.join(',');
|
||||
},
|
||||
strToRecord: (str: string) => {
|
||||
const arr = str.split(',');
|
||||
return arr.reduce((acc, num) => {
|
||||
acc[num] = true;
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>);
|
||||
},
|
||||
arrToStr: <T>(arr: T[]) => {
|
||||
return arr.join(',');
|
||||
},
|
||||
strToArr: (str: string) => {
|
||||
return str.split(',').filter(Boolean) || [];
|
||||
},
|
||||
recordToArr: <T>(rec: Record<string, T>) => {
|
||||
const arr: T[] = [];
|
||||
for (const val of Object.values(rec)) {
|
||||
arr.push(val);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
recordToStrArr: <T>(rec: Record<string, T>) => {
|
||||
const arr: string[] = [];
|
||||
for (const val of Object.keys(rec)) {
|
||||
arr.push(val);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
arrToRecord: (arr: string[]) => {
|
||||
const rec: Record<string, boolean> = {};
|
||||
for (const val of arr) {
|
||||
rec[val] = true;
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
return arr.join(",")
|
||||
},
|
||||
strToRecord: (str: string) => {
|
||||
const arr = str.split(",")
|
||||
return arr.reduce(
|
||||
(acc, num) => {
|
||||
acc[num] = true
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, boolean>,
|
||||
)
|
||||
},
|
||||
arrToStr: <T>(arr: T[]) => {
|
||||
return arr.join(",")
|
||||
},
|
||||
strToArr: (str: string) => {
|
||||
return str.split(",").filter(Boolean) || []
|
||||
},
|
||||
recordToArr: <T>(rec: Record<string, T>) => {
|
||||
const arr: T[] = []
|
||||
for (const val of Object.values(rec)) {
|
||||
arr.push(val)
|
||||
}
|
||||
return arr
|
||||
},
|
||||
recordToStrArr: <T>(rec: Record<string, T>) => {
|
||||
const arr: string[] = []
|
||||
for (const val of Object.keys(rec)) {
|
||||
arr.push(val)
|
||||
}
|
||||
return arr
|
||||
},
|
||||
arrToRecord: (arr: string[]) => {
|
||||
const rec: Record<string, boolean> = {}
|
||||
for (const val of arr) {
|
||||
rec[val] = true
|
||||
}
|
||||
return rec
|
||||
},
|
||||
}
|
||||
|
||||
export const sleep = (ms: number) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
export const fm = {
|
||||
parseFMList: async (buf: ArrayBufferLike) => {
|
||||
const dataView = new DataView(buf);
|
||||
let offset = 4; // Identifier: 4 bytes (NZFN), not needed here
|
||||
|
||||
const pathLength = dataView.getUint32(offset);
|
||||
offset += 4; // File Path Length: 4 bytes
|
||||
|
||||
const pathBuf = new Uint8Array(buf, offset, pathLength);
|
||||
const path = new TextDecoder('utf-8').decode(pathBuf);
|
||||
offset += pathLength; // Path: N bytes
|
||||
|
||||
const fmList: FMEntry[] = [];
|
||||
while (offset < dataView.byteLength) {
|
||||
const fileType = dataView.getUint8(offset);
|
||||
offset += 1; // File Type: 1 byte
|
||||
|
||||
const nameLength = dataView.getUint8(offset);
|
||||
offset += 1; // File Name Length: 1 byte
|
||||
|
||||
const nameBuf = new Uint8Array(buf, offset, nameLength);
|
||||
const name = new TextDecoder('utf-8').decode(nameBuf);
|
||||
offset += nameLength; // File Name: N bytes
|
||||
|
||||
fmList.push({
|
||||
type: fileType,
|
||||
name: name,
|
||||
})
|
||||
}
|
||||
|
||||
return { path, fmList };
|
||||
},
|
||||
|
||||
buildUploadHeader: ({ path, file }: { path: string, file: File }) => {
|
||||
const filePath = `${path}/${file.name}`;
|
||||
|
||||
// Build header (opcode + file size + path)
|
||||
const filePathBytes = new TextEncoder().encode(filePath);
|
||||
const header = new ArrayBuffer(1 + 8 + filePathBytes.length);
|
||||
const headerView = new DataView(header);
|
||||
|
||||
headerView.setUint8(0, FMOpcode.Upload);
|
||||
headerView.setBigUint64(1, BigInt(file.size), false);
|
||||
|
||||
new Uint8Array(header, 9).set(filePathBytes);
|
||||
return header;
|
||||
},
|
||||
|
||||
readFileAsArrayBuffer: async (blob: Blob): Promise<string | ArrayBuffer | null> => {
|
||||
const reader = new FileReader();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = () => reject(reader.error);
|
||||
reader.readAsArrayBuffer(blob);
|
||||
});
|
||||
},
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export const fmWorker = new FMWorker();
|
||||
export const fm = {
|
||||
parseFMList: async (buf: ArrayBufferLike) => {
|
||||
const dataView = new DataView(buf)
|
||||
let offset = 4 // Identifier: 4 bytes (NZFN), not needed here
|
||||
|
||||
const pathLength = dataView.getUint32(offset)
|
||||
offset += 4 // File Path Length: 4 bytes
|
||||
|
||||
const pathBuf = new Uint8Array(buf, offset, pathLength)
|
||||
const path = new TextDecoder("utf-8").decode(pathBuf)
|
||||
offset += pathLength // Path: N bytes
|
||||
|
||||
const fmList: FMEntry[] = []
|
||||
while (offset < dataView.byteLength) {
|
||||
const fileType = dataView.getUint8(offset)
|
||||
offset += 1 // File Type: 1 byte
|
||||
|
||||
const nameLength = dataView.getUint8(offset)
|
||||
offset += 1 // File Name Length: 1 byte
|
||||
|
||||
const nameBuf = new Uint8Array(buf, offset, nameLength)
|
||||
const name = new TextDecoder("utf-8").decode(nameBuf)
|
||||
offset += nameLength // File Name: N bytes
|
||||
|
||||
fmList.push({
|
||||
type: fileType,
|
||||
name: name,
|
||||
})
|
||||
}
|
||||
|
||||
return { path, fmList }
|
||||
},
|
||||
|
||||
buildUploadHeader: ({ path, file }: { path: string; file: File }) => {
|
||||
const filePath = `${path}/${file.name}`
|
||||
|
||||
// Build header (opcode + file size + path)
|
||||
const filePathBytes = new TextEncoder().encode(filePath)
|
||||
const header = new ArrayBuffer(1 + 8 + filePathBytes.length)
|
||||
const headerView = new DataView(header)
|
||||
|
||||
headerView.setUint8(0, FMOpcode.Upload)
|
||||
headerView.setBigUint64(1, BigInt(file.size), false)
|
||||
|
||||
new Uint8Array(header, 9).set(filePathBytes)
|
||||
return header
|
||||
},
|
||||
|
||||
readFileAsArrayBuffer: async (blob: Blob): Promise<string | ArrayBuffer | null> => {
|
||||
const reader = new FileReader()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
reader.onload = () => resolve(reader.result)
|
||||
reader.onerror = () => reject(reader.error)
|
||||
reader.readAsArrayBuffer(blob)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export const fmWorker = new FMWorker()
|
||||
|
||||
export function formatPath(path: string) {
|
||||
return path.replace(/\/{2,}/g, '/');
|
||||
return path.replace(/\/{2,}/g, "/")
|
||||
}
|
||||
|
||||
export function joinIP(p?: ModelIP) {
|
||||
if (p) {
|
||||
if (p.ipv4_addr && p.ipv6_addr) {
|
||||
return `${p.ipv4_addr}/${p.ipv6_addr}`;
|
||||
} else if (p.ipv4_addr) {
|
||||
return p.ipv4_addr;
|
||||
if (p) {
|
||||
if (p.ipv4_addr && p.ipv6_addr) {
|
||||
return `${p.ipv4_addr}/${p.ipv6_addr}`
|
||||
} else if (p.ipv4_addr) {
|
||||
return p.ipv4_addr
|
||||
}
|
||||
return p.ipv6_addr
|
||||
}
|
||||
return p.ipv6_addr;
|
||||
}
|
||||
return '';
|
||||
return ""
|
||||
}
|
||||
|
||||
function base64toUint8Array(base64str: string) {
|
||||
const binary = atob(base64str);
|
||||
const len = binary.length;
|
||||
const buf = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
buf[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
const binary = atob(base64str)
|
||||
const len = binary.length
|
||||
const buf = new Uint8Array(len)
|
||||
for (let i = 0; i < len; i++) {
|
||||
buf[i] = binary.charCodeAt(i)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
export function ip16Str(base64str: string) {
|
||||
const buf = base64toUint8Array(base64str);
|
||||
const ip4 = buf.slice(-6);
|
||||
if (ip4[0] === 255 && ip4[1] === 255) {
|
||||
return ip4.slice(2).join('.');
|
||||
}
|
||||
return ipv6BinaryToString(buf);
|
||||
const buf = base64toUint8Array(base64str)
|
||||
const ip4 = buf.slice(-6)
|
||||
if (ip4[0] === 255 && ip4[1] === 255) {
|
||||
return ip4.slice(2).join(".")
|
||||
}
|
||||
return ipv6BinaryToString(buf)
|
||||
}
|
||||
|
||||
const digits = '0123456789abcdef';
|
||||
const digits = "0123456789abcdef"
|
||||
|
||||
function appendHex(b: string[], x: number): void {
|
||||
if (x >= 0x1000) {
|
||||
b.push(digits[(x >> 12) & 0xf]);
|
||||
}
|
||||
if (x >= 0x100) {
|
||||
b.push(digits[(x >> 8) & 0xf]);
|
||||
}
|
||||
if (x >= 0x10) {
|
||||
b.push(digits[(x >> 4) & 0xf]);
|
||||
}
|
||||
b.push(digits[x & 0xf]);
|
||||
if (x >= 0x1000) {
|
||||
b.push(digits[(x >> 12) & 0xf])
|
||||
}
|
||||
if (x >= 0x100) {
|
||||
b.push(digits[(x >> 8) & 0xf])
|
||||
}
|
||||
if (x >= 0x10) {
|
||||
b.push(digits[(x >> 4) & 0xf])
|
||||
}
|
||||
b.push(digits[x & 0xf])
|
||||
}
|
||||
|
||||
function ipv6BinaryToString(ip: Uint8Array): string {
|
||||
let ipBytes: Uint8Array;
|
||||
let ipBytes: Uint8Array
|
||||
|
||||
if (ip.length !== 16) {
|
||||
ipBytes = new Uint8Array(16);
|
||||
const len = Math.min(ip.length, 16);
|
||||
ipBytes.set(ip.subarray(0, len));
|
||||
} else {
|
||||
ipBytes = ip;
|
||||
}
|
||||
|
||||
const hextets: number[] = [];
|
||||
for (let i = 0; i < 16; i += 2) {
|
||||
hextets.push((ipBytes[i] << 8) | ipBytes[i + 1]);
|
||||
}
|
||||
|
||||
let zeroStart = -1;
|
||||
let zeroLength = 0;
|
||||
|
||||
for (let i = 0; i <= hextets.length;) {
|
||||
let j = i;
|
||||
while (j < hextets.length && hextets[j] === 0) {
|
||||
j++;
|
||||
}
|
||||
const length = j - i;
|
||||
if (length >= 2 && length > zeroLength) {
|
||||
zeroStart = i;
|
||||
zeroLength = length;
|
||||
}
|
||||
if (j === i) {
|
||||
i++;
|
||||
if (ip.length !== 16) {
|
||||
ipBytes = new Uint8Array(16)
|
||||
const len = Math.min(ip.length, 16)
|
||||
ipBytes.set(ip.subarray(0, len))
|
||||
} else {
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
for (let i = 0; i < hextets.length; i++) {
|
||||
if (zeroLength > 0 && i === zeroStart) {
|
||||
parts.push('');
|
||||
i += zeroLength - 1;
|
||||
continue;
|
||||
ipBytes = ip
|
||||
}
|
||||
|
||||
if (parts.length > 0) {
|
||||
parts.push(':');
|
||||
const hextets: number[] = []
|
||||
for (let i = 0; i < 16; i += 2) {
|
||||
hextets.push((ipBytes[i] << 8) | ipBytes[i + 1])
|
||||
}
|
||||
|
||||
const b: string[] = [];
|
||||
appendHex(b, hextets[i]);
|
||||
parts.push(b.join(''));
|
||||
}
|
||||
let zeroStart = -1
|
||||
let zeroLength = 0
|
||||
|
||||
let ipv6 = parts.join('');
|
||||
for (let i = 0; i <= hextets.length; ) {
|
||||
let j = i
|
||||
while (j < hextets.length && hextets[j] === 0) {
|
||||
j++
|
||||
}
|
||||
const length = j - i
|
||||
if (length >= 2 && length > zeroLength) {
|
||||
zeroStart = i
|
||||
zeroLength = length
|
||||
}
|
||||
if (j === i) {
|
||||
i++
|
||||
} else {
|
||||
i = j
|
||||
}
|
||||
}
|
||||
|
||||
if (ipv6.startsWith('::')) {
|
||||
const parts: string[] = []
|
||||
for (let i = 0; i < hextets.length; i++) {
|
||||
if (zeroLength > 0 && i === zeroStart) {
|
||||
parts.push("")
|
||||
i += zeroLength - 1
|
||||
continue
|
||||
}
|
||||
|
||||
} else if (ipv6.startsWith(':')) {
|
||||
ipv6 = ':' + ipv6;
|
||||
}
|
||||
if (ipv6.endsWith('::')) {
|
||||
if (parts.length > 0) {
|
||||
parts.push(":")
|
||||
}
|
||||
|
||||
} else if (ipv6.endsWith(':')) {
|
||||
ipv6 = ipv6 + ':';
|
||||
}
|
||||
if (ipv6 === '') {
|
||||
ipv6 = '::';
|
||||
}
|
||||
const b: string[] = []
|
||||
appendHex(b, hextets[i])
|
||||
parts.push(b.join(""))
|
||||
}
|
||||
|
||||
return ipv6;
|
||||
let ipv6 = parts.join("")
|
||||
|
||||
if (ipv6.startsWith("::")) {
|
||||
} else if (ipv6.startsWith(":")) {
|
||||
ipv6 = ":" + ipv6
|
||||
}
|
||||
if (ipv6.endsWith("::")) {
|
||||
} else if (ipv6.endsWith(":")) {
|
||||
ipv6 = ipv6 + ":"
|
||||
}
|
||||
if (ipv6 === "") {
|
||||
ipv6 = "::"
|
||||
}
|
||||
|
||||
return ipv6
|
||||
}
|
||||
|
||||
export async function copyToClipboard(text: string) {
|
||||
try {
|
||||
return await navigator.clipboard.writeText(text);
|
||||
} catch (error) {
|
||||
console.error('navigator', error);
|
||||
}
|
||||
try {
|
||||
return copy(text)
|
||||
} catch (error) {
|
||||
console.error('copy', error);
|
||||
}
|
||||
throw new Error('Failed to copy text to clipboard');
|
||||
}
|
||||
try {
|
||||
return await navigator.clipboard.writeText(text)
|
||||
} catch (error) {
|
||||
console.error("navigator", error)
|
||||
}
|
||||
try {
|
||||
return copy(text)
|
||||
} catch (error) {
|
||||
console.error("copy", error)
|
||||
}
|
||||
throw new Error("Failed to copy text to clipboard")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user