mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-28 07:41:23 +00:00
feat: phase 5 distribution — tauri shell, pwa, desktop release artifacts
- apps/desktop: Tauri 2 thin shell; connect screen stores the deployment URL, webview renders the remote console, tray with Show/Change Server/ Quit, close-to-tray, single-instance - frontend: vite-plugin-pwa generates manifest + Workbox SW (shell precache, /api /auth /mailpit NetworkOnly), install banner on beforeinstallprompt, rasterized PWA icons - release.yml: desktop bundle matrix (linux/macos/windows) attached to GitHub Releases - workspace 0.5.0; Dockerfiles copy the desktop workspace manifest Authored By: TDvorak <[email protected]>
This commit is contained in:
1 parent
23186e38b8
commit
deb56d9a23
55 files changed
+9657
-155
No files matched your search
@@ -5,6 +5,7 @@ WORKDIR /app
|
||||
COPY package.json package-lock.json tsconfig.base.json ./
|
||||
COPY apps/auth/package.json ./apps/auth/package.json
|
||||
COPY apps/cli/package.json ./apps/cli/package.json
|
||||
COPY apps/desktop/package.json ./apps/desktop/package.json
|
||||
COPY apps/frontend/package.json ./apps/frontend/package.json
|
||||
COPY apps/mcp/package.json ./apps/mcp/package.json
|
||||
COPY packages/api-client/package.json ./packages/api-client/package.json
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
<title>Primora</title>
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Primora" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@primora/frontend",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -10,8 +10,8 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@primora/api-client": "0.4.0",
|
||||
"@primora/shared-types": "0.4.0",
|
||||
"@primora/api-client": "0.5.0",
|
||||
"@primora/shared-types": "0.5.0",
|
||||
"better-auth": "^1.5.6",
|
||||
"solid-js": "^1.9.12"
|
||||
},
|
||||
@@ -26,6 +26,7 @@
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^8.0.3",
|
||||
"vite-plugin-pwa": "^1.3.0",
|
||||
"vite-plugin-solid": "^2.11.8",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<rect width="64" height="64" fill="#f4693a"/>
|
||||
<g fill="#ffffff" transform="translate(6.4 6.4) scale(0.8)">
|
||||
<rect x="15" y="14" width="34" height="10" rx="5"/>
|
||||
<rect x="15" y="27" width="23" height="10" rx="5" opacity="0.7"/>
|
||||
<rect x="15" y="40" width="10" height="10" rx="5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 368 B |
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -31,6 +31,7 @@ import {
|
||||
NetworkError,
|
||||
OnboardingModal,
|
||||
ProjectDashboard,
|
||||
PwaInstallBanner,
|
||||
type PaletteCommand,
|
||||
type ViewType,
|
||||
} from "./components";
|
||||
@@ -1500,6 +1501,7 @@ export default function App() {
|
||||
<Show when={platformMessage()}>
|
||||
<div class="message message-neutral mb-5">{platformMessage()}</div>
|
||||
</Show>
|
||||
<PwaInstallBanner />
|
||||
|
||||
<Show
|
||||
when={platform() || isDemo}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Show, createSignal, onCleanup, onMount } from "solid-js";
|
||||
|
||||
// Chromium fires `beforeinstallprompt` once manifest + service worker pass
|
||||
// installability checks. Stash the event and let the banner trigger it —
|
||||
// the native mini-infobar is easy to miss.
|
||||
interface BeforeInstallPromptEvent extends Event {
|
||||
prompt(): Promise<void>;
|
||||
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
|
||||
}
|
||||
|
||||
const DISMISS_KEY = "primora_pwa_install_dismissed";
|
||||
|
||||
export function PwaInstallBanner() {
|
||||
const [deferred, setDeferred] = createSignal<BeforeInstallPromptEvent | null>(null);
|
||||
const [dismissed, setDismissed] = createSignal(localStorage.getItem(DISMISS_KEY) === "1");
|
||||
|
||||
onMount(() => {
|
||||
const handler = (event: Event) => {
|
||||
event.preventDefault();
|
||||
// SAFETY: the listener is only attached to "beforeinstallprompt", whose
|
||||
// event always carries prompt()/userChoice.
|
||||
setDeferred(event as BeforeInstallPromptEvent);
|
||||
};
|
||||
window.addEventListener("beforeinstallprompt", handler);
|
||||
onCleanup(() => window.removeEventListener("beforeinstallprompt", handler));
|
||||
});
|
||||
|
||||
const install = async () => {
|
||||
const event = deferred();
|
||||
if (!event) return;
|
||||
await event.prompt();
|
||||
if ((await event.userChoice).outcome === "accepted") setDeferred(null);
|
||||
};
|
||||
|
||||
const dismiss = () => {
|
||||
localStorage.setItem(DISMISS_KEY, "1");
|
||||
setDismissed(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Show when={deferred() && !dismissed()}>
|
||||
<div
|
||||
class="message message-info mb-5"
|
||||
style="display:flex;align-items:center;justify-content:space-between;gap:1rem"
|
||||
>
|
||||
<span>Install Primora as an app for quick access from your launcher.</span>
|
||||
<span style="display:flex;gap:0.5rem;flex-shrink:0">
|
||||
<button class="btn btn-primary btn-sm" onClick={install}>
|
||||
Install
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm" onClick={dismiss}>
|
||||
Not now
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export { Modal, ModalFooter } from "./Modal";
|
||||
export { NotificationCenter, addNotification, removeNotification, clearNotifications, notify } from "./NotificationCenter";
|
||||
export { OnboardingModal } from "./OnboardingModal";
|
||||
export { Progress, CircularProgress, Spinner } from "./Progress";
|
||||
export { PwaInstallBanner } from "./PwaInstallBanner";
|
||||
export { ProjectDashboard } from "./ProjectDashboard";
|
||||
export { Table, DataTable, Pagination } from "./Table";
|
||||
export { Tabs, TabPanel } from "./Tabs";
|
||||
|
||||
@@ -1,8 +1,56 @@
|
||||
import { defineConfig } from "vite";
|
||||
import solidPlugin from "vite-plugin-solid";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [solidPlugin()],
|
||||
plugins: [
|
||||
solidPlugin(),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
injectRegister: "auto",
|
||||
manifest: {
|
||||
name: "Primora",
|
||||
short_name: "Primora",
|
||||
description:
|
||||
"Self-hosted backend console: storage, collections, API keys, audit logs.",
|
||||
theme_color: "#0b0d11",
|
||||
background_color: "#0b0d11",
|
||||
display: "standalone",
|
||||
start_url: "/",
|
||||
icons: [
|
||||
{ src: "pwa-192x192.png", sizes: "192x192", type: "image/png" },
|
||||
{ src: "pwa-512x512.png", sizes: "512x512", type: "image/png" },
|
||||
{
|
||||
src: "maskable-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
],
|
||||
},
|
||||
workbox: {
|
||||
// Cache the app shell only. /api, /auth and /mailpit are live backends —
|
||||
// never let a service worker answer for them.
|
||||
navigateFallback: "/index.html",
|
||||
navigateFallbackDenylist: [/^\/(api|auth|mailpit)(\/|$)/],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /\/(api|auth|mailpit)(\/|$)/,
|
||||
handler: "NetworkOnly",
|
||||
},
|
||||
{
|
||||
// Geist font files + CSS from Google Fonts — long-lived static assets.
|
||||
urlPattern: /^https:\/\/fonts\.(googleapis|gstatic)\.com\//,
|
||||
handler: "CacheFirst",
|
||||
options: {
|
||||
cacheName: "primora-fonts",
|
||||
expiration: { maxEntries: 20, maxAgeSeconds: 60 * 60 * 24 * 365 },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: Number(process.env.FRONTEND_PORT ?? 3000),
|
||||
@@ -15,4 +63,3 @@ export default defineConfig({
|
||||
target: "esnext",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user