wip fix
This commit is contained in:
parent
2e6c0de9d5
commit
3b553b8349
|
|
@ -2,6 +2,7 @@ import { $ } from "execa";
|
|||
import * as fs from "fs";
|
||||
import { dirAsync, removeAsync, writeAsync } from "fs-jetpack";
|
||||
import { apiContext } from "service-srv";
|
||||
import { deploy } from "utils/deploy";
|
||||
import { dir } from "utils/dir";
|
||||
import { g } from "utils/global";
|
||||
import { restartServer } from "utils/restart";
|
||||
|
|
@ -29,14 +30,14 @@ export const _ = {
|
|||
const path = dir(`app/web/`);
|
||||
await dirAsync(path);
|
||||
|
||||
const web = g.web;
|
||||
|
||||
switch (action.type) {
|
||||
case "check":
|
||||
const deploys = fs.readdirSync(dir(`/app/web/deploy`));
|
||||
|
||||
return {
|
||||
now: Date.now(),
|
||||
current: 0,
|
||||
deploys: [],
|
||||
current: parseInt(g.deploy.config.deploy.ts),
|
||||
deploys: deploys.map((e) => parseInt(e.replace(".gz", ""))),
|
||||
db: {
|
||||
url: g.dburl || "-",
|
||||
},
|
||||
|
|
@ -66,6 +67,7 @@ datasource db {
|
|||
url = env("DATABASE_URL")
|
||||
}`
|
||||
);
|
||||
|
||||
await $({ cwd: dir("app/db") })`bun install`;
|
||||
await $({ cwd: dir("app/db") })`bun prisma db pull`;
|
||||
await $({ cwd: dir("app/db") })`bun prisma generate`;
|
||||
|
|
@ -85,14 +87,13 @@ datasource db {
|
|||
break;
|
||||
case "deploy-del":
|
||||
{
|
||||
web.deploys = web.deploys.filter((e) => e !== parseInt(action.ts));
|
||||
try {
|
||||
await removeAsync(`${path}/deploys/${action.ts}`);
|
||||
} catch (e) {}
|
||||
await removeAsync(dir(`/app/web/deploy/${action.ts}.gz`));
|
||||
const deploys = fs.readdirSync(dir(`/app/web/deploy`));
|
||||
|
||||
return {
|
||||
now: Date.now(),
|
||||
current: web.current,
|
||||
deploys: web.deploys,
|
||||
current: parseInt(deploy.config.deploy.ts),
|
||||
deploys: deploys.map((e) => parseInt(e.replace(".gz", ""))),
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
|
@ -100,56 +101,28 @@ datasource db {
|
|||
break;
|
||||
case "deploy":
|
||||
{
|
||||
await fs.promises.mkdir(`${path}/deploys`, { recursive: true });
|
||||
const cur = Date.now();
|
||||
const filePath = `${path}/deploys/${cur}`;
|
||||
web.deploying = {
|
||||
status: "generating",
|
||||
received: 0,
|
||||
total: 0,
|
||||
};
|
||||
if (
|
||||
await downloadFile(action.dlurl, filePath, (rec, total) => {
|
||||
web.deploying = {
|
||||
status: "transfering",
|
||||
received: rec,
|
||||
total: total,
|
||||
};
|
||||
})
|
||||
) {
|
||||
web.deploying.status = "deploying";
|
||||
await fs.promises.writeFile(`${path}/current`, cur.toString());
|
||||
web.current = cur;
|
||||
web.deploys.push(cur);
|
||||
}
|
||||
web.deploying = null;
|
||||
deploy.config.deploy.ts = Date.now() + "";
|
||||
await deploy.init();
|
||||
const deploys = fs.readdirSync(dir(`/app/web/deploy`));
|
||||
|
||||
return {
|
||||
now: Date.now(),
|
||||
current: web.current,
|
||||
deploys: web.deploys,
|
||||
current: parseInt(deploy.config.deploy.ts),
|
||||
deploys: deploys.map((e) => parseInt(e.replace(".gz", ""))),
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "redeploy":
|
||||
{
|
||||
const cur = parseInt(action.ts);
|
||||
const lastcur = web.current;
|
||||
try {
|
||||
if (web.deploys.find((e) => e === cur)) {
|
||||
web.current = cur;
|
||||
await fs.promises.writeFile(`${path}/current`, cur.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
web.current = lastcur;
|
||||
web.deploys = web.deploys.filter((e) => e !== parseInt(action.ts));
|
||||
await removeAsync(`${path}/deploys/${action.ts}`);
|
||||
}
|
||||
deploy.config.deploy.ts = action.ts;
|
||||
await deploy.saveConfig();
|
||||
await deploy.load(action.ts);
|
||||
const deploys = fs.readdirSync(dir(`/app/web/deploy`));
|
||||
|
||||
return {
|
||||
now: Date.now(),
|
||||
current: web.current,
|
||||
deploys: web.deploys,
|
||||
current: parseInt(deploy.config.deploy.ts),
|
||||
deploys: deploys.map((e) => parseInt(e.replace(".gz", ""))),
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { readAsync } from "fs-jetpack";
|
|||
import { apiContext } from "service-srv";
|
||||
import { g } from "utils/global";
|
||||
import { dir } from "utils/dir";
|
||||
import { gzipAsync } from "utils/gzip";
|
||||
|
||||
const generated = {
|
||||
"load.json": "",
|
||||
|
|
@ -14,11 +15,83 @@ export const _ = {
|
|||
async api() {
|
||||
const { req, res } = apiContext(this);
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Headers", "content-type");
|
||||
const gz = g.deploy.gz;
|
||||
const parts = req.params._.split("/");
|
||||
|
||||
const action = {
|
||||
_: () => {
|
||||
res.send({ prasi: "v2" });
|
||||
},
|
||||
route: async () => {
|
||||
if (gz) {
|
||||
let layout = null as any;
|
||||
for (const l of gz.layouts) {
|
||||
if (!layout) layout = l;
|
||||
if (l.is_default_layout) layout = l;
|
||||
}
|
||||
|
||||
const result = await gzipAsync(
|
||||
JSON.stringify({
|
||||
site: { ...gz.site, api_url: (gz.site as any)?.config?.api_url },
|
||||
urls: gz.pages.map((e) => {
|
||||
return { id: e.id, url: e.url };
|
||||
}),
|
||||
layout,
|
||||
})
|
||||
);
|
||||
|
||||
return new Response(result, { headers: res.headers });
|
||||
}
|
||||
},
|
||||
page: async () => {
|
||||
const page = g.deploy.pages[parts[1]];
|
||||
if (page) {
|
||||
const result = await gzipAsync(
|
||||
JSON.stringify({
|
||||
id: page.id,
|
||||
root: page.content_tree,
|
||||
url: page.url,
|
||||
})
|
||||
);
|
||||
|
||||
return new Response(result, { headers: res.headers });
|
||||
}
|
||||
},
|
||||
pages: async () => {
|
||||
const pages = [];
|
||||
if (req.params.ids) {
|
||||
for (const id of req.params.ids) {
|
||||
const page = g.deploy.pages[id];
|
||||
if (page) {
|
||||
pages.push({
|
||||
id: page.id,
|
||||
root: page.content_tree,
|
||||
url: page.url,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(await gzipAsync(JSON.stringify(pages)), {
|
||||
headers: res.headers,
|
||||
});
|
||||
},
|
||||
comp: async () => {
|
||||
const comps = {} as Record<string, any>;
|
||||
if (req.params.ids) {
|
||||
for (const id of req.params.ids) {
|
||||
const comp = g.deploy.comps[id];
|
||||
if (comp) {
|
||||
comps[id] = comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(await gzipAsync(JSON.stringify(comps)), {
|
||||
headers: res.headers,
|
||||
});
|
||||
},
|
||||
"load.json": async () => {
|
||||
res.setHeader("content-type", "application/json");
|
||||
res.send(
|
||||
|
|
@ -47,11 +120,11 @@ export const _ = {
|
|||
},
|
||||
};
|
||||
|
||||
const pathname: keyof typeof action = req.params._.split("/")[0] as any;
|
||||
const pathname: keyof typeof action = parts[0] as any;
|
||||
const run = action[pathname];
|
||||
|
||||
if (run) {
|
||||
await run();
|
||||
return await run();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -112,7 +185,7 @@ const getContent = async (type: keyof typeof generated, url?: string) => {
|
|||
}
|
||||
w.prasiApi[url] = {
|
||||
apiEntry: ${JSON.stringify(getApiEntry())},
|
||||
});
|
||||
}
|
||||
})();`;
|
||||
}
|
||||
return generated[type];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { gzipSync } from "bun";
|
||||
import brotliPromise from "brotli-wasm"; // Import the default export
|
||||
import { simpleHash } from "utils/cache";
|
||||
import { g } from "utils/global";
|
||||
const brotli = await brotliPromise;
|
||||
|
||||
const parseQueryParams = (ctx: any) => {
|
||||
|
|
@ -38,12 +39,6 @@ export const apiContext = (ctx: any) => {
|
|||
};
|
||||
};
|
||||
|
||||
const cache = {
|
||||
gz: {} as Record<string, Uint8Array>,
|
||||
br: {} as Record<string, Uint8Array>,
|
||||
br_timeout: new Set<string>(),
|
||||
};
|
||||
|
||||
export const createResponse = (
|
||||
existingRes: any,
|
||||
body: any,
|
||||
|
|
@ -57,31 +52,18 @@ export const createResponse = (
|
|||
if (cache_accept) {
|
||||
const content_hash = simpleHash(content);
|
||||
if (cache_accept.toLowerCase().includes("br")) {
|
||||
if (cache.br[content_hash]) {
|
||||
content = cache.br[content_hash];
|
||||
if (g.cache.br[content_hash]) {
|
||||
content = g.cache.br[content_hash];
|
||||
headers["content-encoding"] = "br";
|
||||
} else {
|
||||
if (!cache.br_timeout.has(content_hash)) {
|
||||
cache.br_timeout.add(content_hash);
|
||||
if (!g.cache.br_timeout.has(content_hash)) {
|
||||
g.cache.br_timeout.add(content_hash);
|
||||
setTimeout(() => {
|
||||
cache.br[content_hash] = brotli.compress(Buffer.from(content));
|
||||
cache.br_timeout.delete(content_hash);
|
||||
g.cache.br[content_hash] = brotli.compress(Buffer.from(content));
|
||||
g.cache.br_timeout.delete(content_hash);
|
||||
});
|
||||
}
|
||||
}
|
||||
headers["content-encoding"] = "br";
|
||||
}
|
||||
|
||||
if (
|
||||
cache_accept.toLowerCase().includes("gz") &&
|
||||
!headers["content-encoding"]
|
||||
) {
|
||||
if (cache.gz[content_hash]) {
|
||||
content = cache.gz[content_hash];
|
||||
} else {
|
||||
cache.gz[content_hash] = gzipSync(content);
|
||||
content = cache.gz[content_hash];
|
||||
}
|
||||
headers["content-encoding"] = "gzip";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,33 +76,17 @@ export const createResponse = (
|
|||
: undefined
|
||||
);
|
||||
|
||||
if (typeof body === "object") {
|
||||
res.headers.append("content-type", "application/json");
|
||||
}
|
||||
for (const [k, v] of Object.entries(headers)) {
|
||||
res.headers.append(k, v);
|
||||
}
|
||||
|
||||
const cur = existingRes as Response;
|
||||
cur.headers.forEach((value, key) => {
|
||||
res.headers.append(key, value);
|
||||
});
|
||||
|
||||
if (typeof body === "object" && !res.headers.has("content-type")) {
|
||||
res.headers.append("content-type", "application/json");
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const simpleHash = (str: string, seed = 0) => {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
import { file } from "bun";
|
||||
import { inspectAsync, listAsync } from "fs-jetpack";
|
||||
import { existsAsync, inspectAsync, listAsync } from "fs-jetpack";
|
||||
import { join } from "path";
|
||||
import { createRouter } from "radix3";
|
||||
import { dir } from "../utils/dir";
|
||||
import { g } from "../utils/global";
|
||||
import { parseArgs } from "./parse-args";
|
||||
import { serveAPI } from "./serve-api";
|
||||
import { serveWeb } from "./serve-web";
|
||||
|
||||
export const createServer = async () => {
|
||||
g.router = createRouter({ strictTrailingSlash: true });
|
||||
g.api = {};
|
||||
g.cache = {
|
||||
br: {},
|
||||
br_timeout: new Set(),
|
||||
};
|
||||
const scan = async (path: string, root?: string) => {
|
||||
const apis = await listAsync(path);
|
||||
if (apis) {
|
||||
|
|
@ -64,13 +69,59 @@ export const createServer = async () => {
|
|||
return api;
|
||||
}
|
||||
|
||||
if (g.deploy.gz && g.deploy.index) {
|
||||
const core = g.deploy.gz.code.core;
|
||||
const site = g.deploy.gz.code.site;
|
||||
|
||||
let pathname = url.pathname;
|
||||
if (url.pathname[0] === "/") pathname = pathname.substring(1);
|
||||
|
||||
if (
|
||||
!pathname ||
|
||||
pathname === "index.html" ||
|
||||
pathname === "index.htm"
|
||||
) {
|
||||
return await serveWeb({
|
||||
content: g.deploy.index.render(),
|
||||
pathname: "index.html",
|
||||
});
|
||||
}
|
||||
|
||||
let content = "";
|
||||
if (core[pathname]) content = core[pathname];
|
||||
else if (site[pathname]) content = site[pathname];
|
||||
|
||||
if (content) {
|
||||
return await serveWeb({ content, pathname });
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(`404 Not Found`, {
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
});
|
||||
};
|
||||
|
||||
if (g.deploy.gz?.code.server) {
|
||||
if (!url.pathname.startsWith("/_deploy")) {
|
||||
if (
|
||||
!g.deploy.server &&
|
||||
(await existsAsync(dir(`app/web/server/index.js`)))
|
||||
) {
|
||||
const res = require(dir(`app/web/server/index.js`));
|
||||
if (res && res.server) {
|
||||
g.deploy.server = res.server;
|
||||
}
|
||||
}
|
||||
if (g.deploy.server && g.deploy.index) {
|
||||
return await g.deploy.server.http({
|
||||
handle,
|
||||
mode: "prod",
|
||||
req,
|
||||
server: g.server,
|
||||
url: { pathname: url.pathname, raw: url },
|
||||
index: g.deploy.index,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return handle(req);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,11 @@
|
|||
import { statSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { dir } from "utils/dir";
|
||||
import mime from "mime";
|
||||
|
||||
export const serveWeb = async (url: URL, req: Request) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const generateIndexHtml = (base_url: string, site_id: string) => {
|
||||
const base = base_url.endsWith("/")
|
||||
? base_url.substring(0, base_url.length - 1)
|
||||
: base_url;
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title></title>
|
||||
<link rel="stylesheet" href="${base}/index.css?fresh">
|
||||
</head>
|
||||
<body class="flex-col flex-1 w-full min-h-screen flex opacity-0">
|
||||
<div style="position:absolute;opacity:0.1;top:0;left:0"> </div>
|
||||
<div id="root"></div>
|
||||
<script src="${base}/site.js" type="module"></script>
|
||||
<script>window.id_site = "${site_id}";</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
|
||||
export const serveWeb = async (arg: { pathname: string; content: string }) => {
|
||||
const type = mime.getType(arg.pathname);
|
||||
|
||||
return new Response(arg.content, {
|
||||
headers: !type ? undefined : { "content-type": type },
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
export const simpleHash = (str: string, seed = 0) => {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString();
|
||||
};
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { dirAsync, read } from "fs-jetpack";
|
||||
import { dirAsync, read, removeAsync, writeAsync } from "fs-jetpack";
|
||||
import { dir } from "./dir";
|
||||
import { g } from "./global";
|
||||
import { gunzipAsync } from "./gzip";
|
||||
import { createRouter } from "radix3";
|
||||
import { prodIndex } from "./prod-index";
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
export const deploy = {
|
||||
|
|
@ -29,6 +30,8 @@ export const deploy = {
|
|||
)
|
||||
);
|
||||
|
||||
g.deploy.index = prodIndex(this.config.site_id);
|
||||
|
||||
if (g.deploy.gz) {
|
||||
for (const page of g.deploy.gz.layouts) {
|
||||
if (page.is_default_layout) {
|
||||
|
|
@ -41,7 +44,9 @@ export const deploy = {
|
|||
}
|
||||
|
||||
g.deploy.router = createRouter();
|
||||
g.deploy.pages = {};
|
||||
for (const page of g.deploy.gz.pages) {
|
||||
g.deploy.pages[page.id] = page;
|
||||
g.deploy.router.insert(page.url, page);
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +54,19 @@ export const deploy = {
|
|||
for (const comp of g.deploy.gz.comps) {
|
||||
g.deploy.comps[comp.id] = comp.content_tree;
|
||||
}
|
||||
|
||||
if (g.deploy.gz.code.server) {
|
||||
setTimeout(async () => {
|
||||
if (g.deploy.gz) {
|
||||
delete require.cache[dir(`app/web/server/index.js`)];
|
||||
await removeAsync(dir(`app/web/server`));
|
||||
await dirAsync(dir(`app/web/server`));
|
||||
for (const [k, v] of Object.entries(g.deploy.gz.code.server)) {
|
||||
await writeAsync(dir(`app/web/server/${k}`), v);
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Failed to load site", this.config.site_id);
|
||||
|
|
@ -82,11 +100,14 @@ export const deploy = {
|
|||
g.deploy = {
|
||||
comps: {},
|
||||
layout: null,
|
||||
pages: {},
|
||||
router: createRouter(),
|
||||
config: { deploy: { ts: "" }, site_id: "" },
|
||||
init: false,
|
||||
raw: null,
|
||||
gz: null as any,
|
||||
gz: null,
|
||||
server: null,
|
||||
index: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { Server } from "bun";
|
||||
import { Server, WebSocketHandler } from "bun";
|
||||
import { Logger } from "pino";
|
||||
import { RadixRouter } from "radix3";
|
||||
import { PrismaClient } from "../../app/db/db";
|
||||
|
||||
import admin from "firebase-admin";
|
||||
import { Database } from "bun:sqlite";
|
||||
import { prodIndex } from "./prod-index";
|
||||
|
||||
type SingleRoute = {
|
||||
url: string;
|
||||
|
|
@ -13,6 +14,18 @@ type SingleRoute = {
|
|||
path: string;
|
||||
};
|
||||
|
||||
type PrasiServer = {
|
||||
ws?: WebSocketHandler<{ url: string }>;
|
||||
http: (arg: {
|
||||
url: { raw: URL; pathname: string };
|
||||
req: Request;
|
||||
server: Server;
|
||||
mode: "dev" | "prod";
|
||||
handle: (req: Request) => Promise<Response>;
|
||||
index: { head: string[]; body: string[]; render: () => string };
|
||||
}) => Promise<Response>;
|
||||
};
|
||||
|
||||
export const g = global as unknown as {
|
||||
db: PrismaClient;
|
||||
dburl: string;
|
||||
|
|
@ -39,12 +52,20 @@ export const g = global as unknown as {
|
|||
js: string;
|
||||
etag: string;
|
||||
};
|
||||
cache: {
|
||||
br: Record<string, Uint8Array>;
|
||||
br_timeout: Set<string>;
|
||||
};
|
||||
deploy: {
|
||||
init: boolean;
|
||||
raw: any;
|
||||
router: RadixRouter<{ url: string; id: string }>;
|
||||
router?: RadixRouter<{ url: string; id: string }>;
|
||||
layout: null | any;
|
||||
comps: Record<string, any>;
|
||||
pages: Record<
|
||||
string,
|
||||
{ id: string; url: string; name: true; content_tree: any }
|
||||
>;
|
||||
gz: null | {
|
||||
layouts: {
|
||||
id: string;
|
||||
|
|
@ -66,5 +87,7 @@ export const g = global as unknown as {
|
|||
site_id: string;
|
||||
deploy: { ts: string };
|
||||
};
|
||||
server: PrasiServer | null;
|
||||
index: ReturnType<typeof prodIndex> | null;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
export const prodIndex = (site_id: string) => {
|
||||
return {
|
||||
head: [] as string[],
|
||||
body: [] as string[],
|
||||
render() {
|
||||
return `\
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=1.0, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
${this.head.join("\n")}
|
||||
</head>
|
||||
|
||||
<body class="flex-col flex-1 w-full min-h-screen flex opacity-0">
|
||||
${this.body.join("\n")}
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
window._prasi = { basepath: "/", site_id: "${site_id}" }
|
||||
</script>
|
||||
<script src="/main.js" type="module"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
const decoder = new TextDecoder();
|
||||
|
||||
function humanFileSize(bytes: any, si = false, dp = 1) {
|
||||
export function humanFileSize(bytes: any, si = false, dp = 1) {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
Loading…
Reference in New Issue