mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-21 16:14:28 +08:00
0.0.1
This commit is contained in:
448
out/main/index.js
Normal file
448
out/main/index.js
Normal file
@@ -0,0 +1,448 @@
|
||||
"use strict";
|
||||
const electron = require("electron");
|
||||
const path = require("path");
|
||||
const utils = require("@electron-toolkit/utils");
|
||||
const elysia = require("elysia");
|
||||
const fs = require("fs");
|
||||
const child_process = require("child_process");
|
||||
const util = require("util");
|
||||
const node = require("@elysiajs/node");
|
||||
const icon = path.join(__dirname, "../../resources/icon.png");
|
||||
function getStartMenuPaths() {
|
||||
const appData = process.env["APPDATA"];
|
||||
const programData = process.env["ProgramData"] ?? process.env["PROGRAMDATA"];
|
||||
return {
|
||||
userProgramsPath: appData ? path.join(appData, "Microsoft", "Windows", "Start Menu", "Programs") : null,
|
||||
commonProgramsPath: programData ? path.join(programData, "Microsoft", "Windows", "Start Menu", "Programs") : null
|
||||
};
|
||||
}
|
||||
function getStartMenuRoots() {
|
||||
const { userProgramsPath, commonProgramsPath } = getStartMenuPaths();
|
||||
return [userProgramsPath, commonProgramsPath].filter((p) => Boolean(p));
|
||||
}
|
||||
const execFileAsync$1 = util.promisify(child_process.execFile);
|
||||
function getWindowsPowerShellExe() {
|
||||
const systemRoot = process.env["SystemRoot"] ?? process.env["WINDIR"] ?? "C:\\Windows";
|
||||
return path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
||||
}
|
||||
function buildPowerShellScript(roots) {
|
||||
const rootsJson = JSON.stringify(roots).replace(/'/g, "''");
|
||||
return [
|
||||
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
|
||||
'$ErrorActionPreference = "Stop"',
|
||||
"$out = $null",
|
||||
"try {",
|
||||
`$roots = '${rootsJson}' | ConvertFrom-Json`,
|
||||
"$wsh = New-Object -ComObject WScript.Shell",
|
||||
"$entries = @()",
|
||||
"foreach ($root in $roots) {",
|
||||
" if (-not (Test-Path -LiteralPath $root)) { continue }",
|
||||
" Get-ChildItem -LiteralPath $root -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {",
|
||||
" $ext = $_.Extension.ToLowerInvariant()",
|
||||
' if ($ext -ne ".lnk" -and $ext -ne ".url" -and $ext -ne ".appref-ms") { return }',
|
||||
' $source = "unknown"',
|
||||
' if ($root -like "*\\\\AppData\\\\Roaming*") { $source = "user" }',
|
||||
' if ($root -like "*\\\\ProgramData*") { $source = "common" }',
|
||||
' $rel = $_.FullName.Substring($root.Length).TrimStart("\\\\")',
|
||||
' if ($ext -eq ".lnk") {',
|
||||
" $sc = $wsh.CreateShortcut($_.FullName)",
|
||||
" $entries += [pscustomobject]@{",
|
||||
" id = $_.FullName",
|
||||
" name = $_.BaseName",
|
||||
' type = "lnk"',
|
||||
" filePath = $_.FullName",
|
||||
" relativePath = $rel",
|
||||
" targetPath = $sc.TargetPath",
|
||||
" arguments = $sc.Arguments",
|
||||
" workingDirectory = $sc.WorkingDirectory",
|
||||
" iconLocation = $sc.IconLocation",
|
||||
" description = $sc.Description",
|
||||
" source = $source",
|
||||
" }",
|
||||
" return",
|
||||
" }",
|
||||
' $t = "appref-ms"',
|
||||
' if ($ext -eq ".url") { $t = "url" }',
|
||||
" $entries += [pscustomobject]@{",
|
||||
" id = $_.FullName",
|
||||
" name = $_.BaseName",
|
||||
" type = $t",
|
||||
" filePath = $_.FullName",
|
||||
" relativePath = $rel",
|
||||
" source = $source",
|
||||
" }",
|
||||
" }",
|
||||
"}",
|
||||
"$usedStartApps = $false",
|
||||
"try {",
|
||||
" $startApps = Get-StartApps | Select-Object Name, AppID",
|
||||
" if ($startApps -ne $null) {",
|
||||
" $usedStartApps = $true",
|
||||
" foreach ($a in $startApps) {",
|
||||
" if ([string]::IsNullOrWhiteSpace($a.AppID)) { continue }",
|
||||
" $entries += [pscustomobject]@{",
|
||||
' id = "appsFolder:" + $a.AppID',
|
||||
" name = $a.Name",
|
||||
' type = "uwp"',
|
||||
' filePath = "shell:AppsFolder\\\\" + $a.AppID',
|
||||
' relativePath = "AppsFolder\\\\" + $a.Name',
|
||||
" appUserModelId = $a.AppID",
|
||||
' source = "appsfolder"',
|
||||
" }",
|
||||
" }",
|
||||
" }",
|
||||
"} catch { }",
|
||||
"if (-not $usedStartApps) {",
|
||||
" $shell = New-Object -ComObject Shell.Application",
|
||||
' $appsFolder = $shell.NameSpace("shell:AppsFolder")',
|
||||
" if ($appsFolder -ne $null) {",
|
||||
" $appsFolder.Items() | ForEach-Object {",
|
||||
" $aumid = $_.Path",
|
||||
" if ([string]::IsNullOrWhiteSpace($aumid)) { return }",
|
||||
" $name = $_.Name",
|
||||
" $entries += [pscustomobject]@{",
|
||||
' id = "appsFolder:" + $aumid',
|
||||
" name = $name",
|
||||
' type = "uwp"',
|
||||
' filePath = "shell:AppsFolder\\\\" + $aumid',
|
||||
' relativePath = "AppsFolder\\\\" + $name',
|
||||
" appUserModelId = $aumid",
|
||||
' source = "appsfolder"',
|
||||
" }",
|
||||
" }",
|
||||
" }",
|
||||
"}",
|
||||
"$out = [pscustomobject]@{ ok = $true; entries = $entries }",
|
||||
"} catch {",
|
||||
" $out = [pscustomobject]@{ ok = $false; error = ($_ | Out-String); entries = @() }",
|
||||
"}",
|
||||
"$out | ConvertTo-Json -Depth 6"
|
||||
].join("\n");
|
||||
}
|
||||
async function listWindowsStartMenuApps() {
|
||||
if (process.platform !== "win32") return [];
|
||||
const roots = getStartMenuRoots();
|
||||
const script = buildPowerShellScript(roots);
|
||||
const powershellExe = getWindowsPowerShellExe();
|
||||
const { stdout } = await execFileAsync$1(
|
||||
powershellExe,
|
||||
["-NoProfile", "-NonInteractive", "-Sta", "-ExecutionPolicy", "Bypass", "-Command", script],
|
||||
{ windowsHide: true, maxBuffer: 50 * 1024 * 1024, timeout: 6e4 }
|
||||
);
|
||||
const trimmed = stdout.trim();
|
||||
if (!trimmed) return [];
|
||||
const parsed = JSON.parse(trimmed);
|
||||
if (typeof parsed === "object" && parsed !== null && "ok" in parsed) {
|
||||
const ok = parsed.ok;
|
||||
if (ok === false) {
|
||||
const error = parsed.error;
|
||||
throw new Error(typeof error === "string" ? error : "PowerShellFailed");
|
||||
}
|
||||
}
|
||||
const rawEntries = typeof parsed === "object" && parsed !== null && "entries" in parsed ? parsed.entries : parsed;
|
||||
const list = Array.isArray(rawEntries) ? rawEntries : rawEntries ? [rawEntries] : [];
|
||||
const isEntry = (value) => {
|
||||
if (typeof value !== "object" || value === null) return false;
|
||||
const v = value;
|
||||
return typeof v.id === "string" && typeof v.name === "string" && typeof v.type === "string" && typeof v.filePath === "string" && typeof v.relativePath === "string";
|
||||
};
|
||||
const seen = /* @__PURE__ */ new Set();
|
||||
const result = [];
|
||||
for (const item of list) {
|
||||
if (!isEntry(item)) continue;
|
||||
if (seen.has(item.id)) continue;
|
||||
seen.add(item.id);
|
||||
result.push(item);
|
||||
}
|
||||
result.sort((a, b) => a.name.localeCompare(b.name, "zh-CN"));
|
||||
return result;
|
||||
}
|
||||
const execFileAsync = util.promisify(child_process.execFile);
|
||||
function isUnderRoot(filePath, root) {
|
||||
const normalizedFile = path.resolve(filePath).toLowerCase();
|
||||
const normalizedRoot = path.resolve(root).toLowerCase();
|
||||
return normalizedFile === normalizedRoot || normalizedFile.startsWith(normalizedRoot + "\\");
|
||||
}
|
||||
async function launchStartMenuEntry(filePath) {
|
||||
if (process.platform !== "win32") return;
|
||||
if (filePath.startsWith("shell:AppsFolder\\")) {
|
||||
await execFileAsync("explorer.exe", [filePath], { windowsHide: true });
|
||||
return;
|
||||
}
|
||||
const roots = getStartMenuRoots();
|
||||
const allowed = roots.some((root) => isUnderRoot(filePath, root));
|
||||
if (!allowed) {
|
||||
throw new Error("PathNotAllowed");
|
||||
}
|
||||
const result = await electron.shell.openPath(filePath);
|
||||
if (result) {
|
||||
throw new Error(result);
|
||||
}
|
||||
}
|
||||
function createEiysiaApp(deps) {
|
||||
const iconCache = /* @__PURE__ */ new Map();
|
||||
const appsCacheFilePath = path.join(electron.app.getPath("userData"), "apps-cache.json");
|
||||
let cachedApps = [];
|
||||
let cacheLoadPromise = null;
|
||||
let refreshPromise = null;
|
||||
const ensureAppsCacheLoaded = async () => {
|
||||
if (cacheLoadPromise) return cacheLoadPromise;
|
||||
cacheLoadPromise = (async () => {
|
||||
try {
|
||||
const raw = await fs.promises.readFile(appsCacheFilePath, "utf-8");
|
||||
const parsed = JSON.parse(raw);
|
||||
const apps = Array.isArray(parsed.apps) ? parsed.apps : [];
|
||||
const parsedApps = apps.map((a) => {
|
||||
const id = typeof a.id === "string" ? a.id : "";
|
||||
const name = typeof a.name === "string" ? a.name : "";
|
||||
const filePath = typeof a.filePath === "string" ? a.filePath : "";
|
||||
const iconDataUrl = typeof a.iconDataUrl === "string" ? a.iconDataUrl : "";
|
||||
if (!id || !name || !filePath || !iconDataUrl) return null;
|
||||
return { id, name, filePath, iconDataUrl };
|
||||
});
|
||||
cachedApps = parsedApps.filter((a) => Boolean(a));
|
||||
} catch {
|
||||
cachedApps = [];
|
||||
}
|
||||
})();
|
||||
return cacheLoadPromise;
|
||||
};
|
||||
const persistAppsCache = async (apps) => {
|
||||
const payload = JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
updatedAt: Date.now(),
|
||||
apps
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
await fs.promises.writeFile(appsCacheFilePath, payload, "utf-8");
|
||||
};
|
||||
const refreshAppsCache = async () => {
|
||||
if (refreshPromise) return refreshPromise;
|
||||
refreshPromise = (async () => {
|
||||
const cachedById = new Map(cachedApps.map((a) => [a.id, a]));
|
||||
const entries = await listWindowsStartMenuApps();
|
||||
let index = 0;
|
||||
const limit = Math.max(4, Math.min(16, entries.length));
|
||||
const nextApps = new Array(entries.length);
|
||||
const workers = Array.from({ length: limit }, async () => {
|
||||
while (true) {
|
||||
const i = index;
|
||||
index += 1;
|
||||
if (i >= entries.length) return;
|
||||
const e = entries[i];
|
||||
const cached = cachedById.get(e.id);
|
||||
if (cached && cached.name === e.name && cached.filePath === e.filePath && cached.iconDataUrl) {
|
||||
nextApps[i] = cached;
|
||||
continue;
|
||||
}
|
||||
const iconDataUrl = await getIconDataUrl(e);
|
||||
nextApps[i] = { id: e.id, name: e.name, filePath: e.filePath, iconDataUrl };
|
||||
}
|
||||
});
|
||||
await Promise.all(workers);
|
||||
cachedApps = nextApps.filter(Boolean);
|
||||
await persistAppsCache(cachedApps);
|
||||
})().finally(() => {
|
||||
refreshPromise = null;
|
||||
});
|
||||
return refreshPromise;
|
||||
};
|
||||
const placeholderIconDataUrl = (name, id) => {
|
||||
const letter = (name.trim().charAt(0) || "?").toUpperCase();
|
||||
let hash = 0;
|
||||
for (let i = 0; i < id.length; i += 1) {
|
||||
hash = hash * 31 + id.charCodeAt(i) | 0;
|
||||
}
|
||||
const hue = Math.abs(hash) % 360;
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><circle cx="32" cy="32" r="32" fill="hsl(${hue} 70% 45%)"/><text x="32" y="40" text-anchor="middle" font-family="Segoe UI, Arial" font-size="28" font-weight="700" fill="white">${letter.replace(
|
||||
/[<>&]/g,
|
||||
""
|
||||
)}</text></svg>`;
|
||||
return `data:image/svg+xml;base64,${Buffer.from(svg, "utf-8").toString("base64")}`;
|
||||
};
|
||||
const getIconDataUrl = async (entry) => {
|
||||
const cached = iconCache.get(entry.id);
|
||||
if (cached) return cached;
|
||||
if (entry.type === "uwp" || entry.filePath.startsWith("shell:")) {
|
||||
const dataUrl = placeholderIconDataUrl(entry.name, entry.id);
|
||||
iconCache.set(entry.id, dataUrl);
|
||||
return dataUrl;
|
||||
}
|
||||
if (!electron.app.isReady()) {
|
||||
const dataUrl = placeholderIconDataUrl(entry.name, entry.id);
|
||||
iconCache.set(entry.id, dataUrl);
|
||||
return dataUrl;
|
||||
}
|
||||
try {
|
||||
const tryPath = typeof entry.targetPath === "string" && entry.targetPath.trim() ? entry.targetPath : entry.filePath;
|
||||
const rawIcon = await electron.app.getFileIcon(tryPath, { size: "large" }).catch(() => electron.app.getFileIcon(tryPath, { size: "normal" }));
|
||||
let icon2 = rawIcon;
|
||||
if (!icon2.isEmpty()) {
|
||||
const { width, height } = icon2.getSize();
|
||||
const target = 64;
|
||||
if (width > 0 && height > 0 && (width < target || height < target)) {
|
||||
icon2 = icon2.resize({ width: target, height: target, quality: "best" });
|
||||
}
|
||||
}
|
||||
const dataUrl = icon2.isEmpty() ? placeholderIconDataUrl(entry.name, entry.id) : icon2.toDataURL();
|
||||
iconCache.set(entry.id, dataUrl);
|
||||
return dataUrl;
|
||||
} catch {
|
||||
const dataUrl = placeholderIconDataUrl(entry.name, entry.id);
|
||||
iconCache.set(entry.id, dataUrl);
|
||||
return dataUrl;
|
||||
}
|
||||
};
|
||||
return new elysia.Elysia().get("/health", () => ({
|
||||
ok: true,
|
||||
time: Date.now()
|
||||
})).onStart(() => {
|
||||
void ensureAppsCacheLoaded().then(() => refreshAppsCache());
|
||||
}).get("/apps/list", async () => {
|
||||
try {
|
||||
await ensureAppsCacheLoaded();
|
||||
if (cachedApps.length === 0) {
|
||||
await refreshAppsCache();
|
||||
} else {
|
||||
void refreshAppsCache();
|
||||
}
|
||||
return { apps: cachedApps, error: null };
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "UnknownError";
|
||||
console.error("[apps/list] failed:", message);
|
||||
return { apps: [], error: message };
|
||||
}
|
||||
}).post("/apps/launch", async ({ body }) => {
|
||||
const payload = body;
|
||||
if (typeof payload.filePath !== "string") {
|
||||
return new Response("BadRequest", { status: 400 });
|
||||
}
|
||||
try {
|
||||
await launchStartMenuEntry(payload.filePath);
|
||||
return { ok: true };
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "UnknownError";
|
||||
if (message === "PathNotAllowed") return new Response("Forbidden", { status: 403 });
|
||||
return new Response("LaunchFailed", { status: 500 });
|
||||
}
|
||||
}).get("/backend/port", () => ({
|
||||
port: deps.getHttpPort()
|
||||
})).post("/app/minimize", () => {
|
||||
deps.getMainWindow()?.minimize();
|
||||
return { ok: true };
|
||||
});
|
||||
}
|
||||
function registerEiysiaIpc(app) {
|
||||
electron.ipcMain.handle(
|
||||
"eiysia:request",
|
||||
async (_event, payload) => {
|
||||
const method = payload.method?.toUpperCase?.() ?? "GET";
|
||||
const path2 = payload.path?.startsWith("/") ? payload.path : `/${payload.path ?? ""}`;
|
||||
const headers = new Headers(payload.headers ?? {});
|
||||
let body;
|
||||
if (payload.body !== void 0) {
|
||||
if (typeof payload.body === "string" || payload.body instanceof ArrayBuffer || ArrayBuffer.isView(payload.body)) {
|
||||
body = payload.body;
|
||||
} else {
|
||||
body = JSON.stringify(payload.body);
|
||||
if (!headers.has("content-type")) {
|
||||
headers.set("content-type", "application/json");
|
||||
}
|
||||
}
|
||||
}
|
||||
const request = new Request(`http://eiysia.local${path2}`, {
|
||||
method,
|
||||
headers,
|
||||
body
|
||||
});
|
||||
const response = await app.handle(request);
|
||||
const bodyText = await response.text();
|
||||
return {
|
||||
status: response.status,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
bodyText
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
function startEiysiaHttpServer(app) {
|
||||
const serverApp = new elysia.Elysia({ adapter: node.node() }).use(app).listen({ hostname: "127.0.0.1", port: 0 });
|
||||
let stopped = false;
|
||||
const stop = () => {
|
||||
if (stopped) return;
|
||||
stopped = true;
|
||||
try {
|
||||
if (!serverApp.server) return;
|
||||
const maybe = serverApp;
|
||||
if (typeof maybe.stop === "function") maybe.stop();
|
||||
else if (typeof maybe.close === "function") maybe.close();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
return {
|
||||
app: serverApp,
|
||||
port: serverApp.server?.port ?? null,
|
||||
stop
|
||||
};
|
||||
}
|
||||
electron.app.commandLine.appendSwitch("touch-events", "enabled");
|
||||
let mainWindow = null;
|
||||
let eiysiaServerStop = null;
|
||||
let eiysiaHttpPort = null;
|
||||
function createWindow() {
|
||||
const window = new electron.BrowserWindow({
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
fullscreen: true,
|
||||
...process.platform === "linux" ? { icon } : {},
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "../preload/index.js"),
|
||||
sandbox: false
|
||||
}
|
||||
});
|
||||
mainWindow = window;
|
||||
window.on("ready-to-show", () => {
|
||||
window.show();
|
||||
});
|
||||
window.webContents.setWindowOpenHandler((details) => {
|
||||
electron.shell.openExternal(details.url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
window.loadURL(process.env["ELECTRON_RENDERER_URL"]);
|
||||
} else {
|
||||
window.loadFile(path.join(__dirname, "../renderer/index.html"));
|
||||
}
|
||||
}
|
||||
electron.app.whenReady().then(() => {
|
||||
utils.electronApp.setAppUserModelId("com.electron");
|
||||
electron.app.on("browser-window-created", (_, window) => {
|
||||
utils.optimizer.watchWindowShortcuts(window);
|
||||
});
|
||||
const eiysia = createEiysiaApp({
|
||||
getMainWindow: () => mainWindow,
|
||||
getHttpPort: () => eiysiaHttpPort
|
||||
});
|
||||
registerEiysiaIpc(eiysia);
|
||||
const server = startEiysiaHttpServer(eiysia);
|
||||
eiysiaHttpPort = server.port;
|
||||
eiysiaServerStop = server.stop;
|
||||
createWindow();
|
||||
electron.app.on("activate", function() {
|
||||
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
electron.app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
electron.app.quit();
|
||||
}
|
||||
});
|
||||
electron.app.on("before-quit", () => {
|
||||
eiysiaServerStop?.();
|
||||
eiysiaServerStop = null;
|
||||
eiysiaHttpPort = null;
|
||||
});
|
||||
25
out/preload/index.js
Normal file
25
out/preload/index.js
Normal file
@@ -0,0 +1,25 @@
|
||||
"use strict";
|
||||
const electron = require("electron");
|
||||
const preload = require("@electron-toolkit/preload");
|
||||
const api = {
|
||||
request: (payload) => electron.ipcRenderer.invoke("eiysia:request", payload),
|
||||
call: async (payload) => {
|
||||
const response = await electron.ipcRenderer.invoke("eiysia:request", payload);
|
||||
const contentType = response.headers["content-type"] ?? response.headers["Content-Type"];
|
||||
if (contentType?.includes("application/json")) {
|
||||
return JSON.parse(response.bodyText);
|
||||
}
|
||||
return response.bodyText;
|
||||
}
|
||||
};
|
||||
if (process.contextIsolated) {
|
||||
try {
|
||||
electron.contextBridge.exposeInMainWorld("electron", preload.electronAPI);
|
||||
electron.contextBridge.exposeInMainWorld("api", api);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
window.electron = preload.electronAPI;
|
||||
window.api = api;
|
||||
}
|
||||
6711
out/renderer/assets/index-C9aAt2YI.js
Normal file
6711
out/renderer/assets/index-C9aAt2YI.js
Normal file
File diff suppressed because it is too large
Load Diff
450
out/renderer/assets/index-DAQiHmWm.css
Normal file
450
out/renderer/assets/index-DAQiHmWm.css
Normal file
@@ -0,0 +1,450 @@
|
||||
:root {
|
||||
--ev-c-white: #ffffff;
|
||||
--ev-c-white-soft: #f8f8f8;
|
||||
--ev-c-white-mute: #f2f2f2;
|
||||
|
||||
--ev-c-black: #1b1b1f;
|
||||
--ev-c-black-soft: #222222;
|
||||
--ev-c-black-mute: #282828;
|
||||
|
||||
--ev-c-gray-1: #515c67;
|
||||
--ev-c-gray-2: #414853;
|
||||
--ev-c-gray-3: #32363f;
|
||||
|
||||
--ev-c-text-1: rgba(255, 255, 245, 0.86);
|
||||
--ev-c-text-2: rgba(235, 235, 245, 0.6);
|
||||
--ev-c-text-3: rgba(235, 235, 245, 0.38);
|
||||
|
||||
--ev-button-alt-border: transparent;
|
||||
--ev-button-alt-text: var(--ev-c-text-1);
|
||||
--ev-button-alt-bg: var(--ev-c-gray-3);
|
||||
--ev-button-alt-hover-border: transparent;
|
||||
--ev-button-alt-hover-text: var(--ev-c-text-1);
|
||||
--ev-button-alt-hover-bg: var(--ev-c-gray-2);
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-background: var(--ev-c-black);
|
||||
--color-background-soft: var(--ev-c-black-soft);
|
||||
--color-background-mute: var(--ev-c-black-mute);
|
||||
|
||||
--color-text: var(--ev-c-text-1);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-image: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201422%20800'%20opacity='0.3'%3e%3cdefs%3e%3clinearGradient%20x1='50%25'%20y1='0%25'%20x2='50%25'%20y2='100%25'%20id='oooscillate-grad'%3e%3cstop%20stop-color='hsl(206,%2075%25,%2049%25)'%20stop-opacity='1'%20offset='0%25'%3e%3c/stop%3e%3cstop%20stop-color='hsl(331,%2090%25,%2056%25)'%20stop-opacity='1'%20offset='100%25'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3cg%20stroke-width='1'%20stroke='url(%23oooscillate-grad)'%20fill='none'%20stroke-linecap='round'%3e%3cpath%20d='M%200%20448%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20448'%20opacity='0.05'%3e%3c/path%3e%3cpath%20d='M%200%20420%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20420'%20opacity='0.11'%3e%3c/path%3e%3cpath%20d='M%200%20392%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20392'%20opacity='0.18'%3e%3c/path%3e%3cpath%20d='M%200%20364%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20364'%20opacity='0.24'%3e%3c/path%3e%3cpath%20d='M%200%20336%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20336'%20opacity='0.30'%3e%3c/path%3e%3cpath%20d='M%200%20308%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20308'%20opacity='0.37'%3e%3c/path%3e%3cpath%20d='M%200%20280%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20280'%20opacity='0.43'%3e%3c/path%3e%3cpath%20d='M%200%20252%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20252'%20opacity='0.49'%3e%3c/path%3e%3cpath%20d='M%200%20224%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20224'%20opacity='0.56'%3e%3c/path%3e%3cpath%20d='M%200%20196%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20196'%20opacity='0.62'%3e%3c/path%3e%3cpath%20d='M%200%20168%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20168'%20opacity='0.68'%3e%3c/path%3e%3cpath%20d='M%200%20140%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20140'%20opacity='0.75'%3e%3c/path%3e%3cpath%20d='M%200%20112%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%20112'%20opacity='0.81'%3e%3c/path%3e%3cpath%20d='M%200%2084%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%2084'%20opacity='0.87'%3e%3c/path%3e%3cpath%20d='M%200%2056%20Q%20355.5%20-100%20711%20400%20Q%201066.5%20900%201422%2056'%20opacity='0.94'%3e%3c/path%3e%3c/g%3e%3c/svg%3e");
|
||||
background-size: cover;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.touchButton {
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.launcher {
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
top: 84px;
|
||||
width: 520px;
|
||||
height: calc(100vh - 168px);
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.28);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 14px;
|
||||
backdrop-filter: blur(12px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.home {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.openAppsButton {
|
||||
cursor: pointer;
|
||||
border-radius: 18px;
|
||||
padding: 20px 28px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
color: var(--ev-c-text-1);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(12px);
|
||||
font-size: 22px;
|
||||
line-height: 26px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.openAppsButton:hover {
|
||||
border-color: rgba(255, 255, 255, 0.26);
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.appsPage {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.backButton {
|
||||
cursor: pointer;
|
||||
border-radius: 16px;
|
||||
padding: 0 18px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: var(--ev-c-text-1);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
font-weight: 800;
|
||||
height: 72px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.backButton:hover {
|
||||
border-color: rgba(255, 255, 255, 0.22);
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.appsTilesArea {
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
top: 88px;
|
||||
bottom: 120px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.28);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 14px;
|
||||
backdrop-filter: blur(12px);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.appsList {
|
||||
height: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: 4px 6px;
|
||||
box-sizing: border-box;
|
||||
columns: 6 280px;
|
||||
column-gap: 14px;
|
||||
column-fill: auto;
|
||||
touch-action: pan-x;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-x: contain;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.appsList::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.appsLoading {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.appsBottomBar {
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
height: 92px;
|
||||
padding: 10px 12px;
|
||||
display: grid;
|
||||
grid-template-columns: 140px 1fr 140px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 16px;
|
||||
backdrop-filter: blur(12px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.appsSearch {
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
color: var(--ev-c-text-1);
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.appsExitButton {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 16px;
|
||||
padding: 0 18px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: var(--ev-c-text-1);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
font-weight: 800;
|
||||
height: 72px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.appsExitButton:hover {
|
||||
border-color: rgba(255, 255, 255, 0.22);
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
color: var(--ev-c-text-1);
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 120, 120, 0.25);
|
||||
background: rgba(120, 0, 0, 0.18);
|
||||
color: var(--ev-c-text-1);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
user-select: text;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.errorHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.errorTitle {
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.errorBody {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
color: var(--ev-c-text-1);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.copyButton:hover {
|
||||
border-color: rgba(255, 255, 255, 0.26);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 12px;
|
||||
height: calc(100% - 50px);
|
||||
overflow: auto;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.groupHeader {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0 0 10px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4px;
|
||||
opacity: 0.9;
|
||||
break-inside: avoid;
|
||||
-webkit-column-break-inside: avoid;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-height: 72px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
color: var(--ev-c-text-1);
|
||||
margin: 0 0 10px;
|
||||
break-inside: avoid;
|
||||
-webkit-column-break-inside: avoid;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
border-color: rgba(255, 255, 255, 0.22);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.item:active {
|
||||
border-color: rgba(255, 255, 255, 0.26);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.path {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
opacity: 0.75;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.clock {
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
padding: 10px 12px;
|
||||
font-size: 28px;
|
||||
line-height: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--ev-c-text-1);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.exitButton {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
border-radius: 14px;
|
||||
padding: 10px 16px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: var(--ev-c-text-1);
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.buttonIcon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.exitButton:hover {
|
||||
border-color: rgba(255, 255, 255, 0.22);
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
18
out/renderer/index.html
Normal file
18
out/renderer/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Electron</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
||||
/>
|
||||
<script type="module" crossorigin src="./assets/index-C9aAt2YI.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-DAQiHmWm.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user