P0: scaffold Vite + Vue 3 + Tauri v2
- pnpm + TS + Tailwind 3 + Pinia + Vue Router with hash history - 6 placeholder views (Status, Peers, Routes, Messages, Topics, Settings) rendered via lucide-icon sidebar in App.vue - Tauri v2: shell, store, log, dialog plugins; bundle targets deb + appimage; sidecar wired via externalBin = binaries/mycelium - scripts/fetch-mycelium.sh pins v0.6.1, maps musl asset onto gnu target triple expected by Tauri bundler - CI: pnpm typecheck + cargo fmt/clippy/test
This commit is contained in:
61
src/App.vue
Normal file
61
src/App.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { RouterLink, RouterView, useRoute } from "vue-router";
|
||||
import {
|
||||
Activity,
|
||||
Users,
|
||||
Route as RouteIcon,
|
||||
MessageSquare,
|
||||
Hash,
|
||||
Settings as SettingsIcon,
|
||||
} from "lucide-vue-next";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const navItems = [
|
||||
{ to: "/status", label: "Status", icon: Activity },
|
||||
{ to: "/peers", label: "Peers", icon: Users },
|
||||
{ to: "/routes", label: "Routes", icon: RouteIcon },
|
||||
{ to: "/messages", label: "Messages", icon: MessageSquare },
|
||||
{ to: "/topics", label: "Topics", icon: Hash },
|
||||
{ to: "/settings", label: "Settings", icon: SettingsIcon },
|
||||
];
|
||||
|
||||
const currentTitle = computed(() => (route.meta?.title as string) ?? "Mycellium");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-screen w-screen overflow-hidden bg-background text-foreground">
|
||||
<aside
|
||||
class="flex w-56 shrink-0 flex-col border-r border-border bg-card"
|
||||
>
|
||||
<div class="flex h-14 items-center px-4 border-b border-border">
|
||||
<span class="font-semibold text-base">Mycellium</span>
|
||||
</div>
|
||||
<nav class="flex-1 overflow-y-auto px-2 py-3 space-y-1">
|
||||
<RouterLink
|
||||
v-for="item in navItems"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
class="flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors"
|
||||
active-class="bg-secondary text-secondary-foreground"
|
||||
exact-active-class="bg-secondary text-secondary-foreground"
|
||||
>
|
||||
<component :is="item.icon" class="h-4 w-4" />
|
||||
<span>{{ item.label }}</span>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="flex flex-1 flex-col overflow-hidden">
|
||||
<header
|
||||
class="flex h-14 items-center border-b border-border px-6 shrink-0"
|
||||
>
|
||||
<h1 class="text-lg font-semibold">{{ currentTitle }}</h1>
|
||||
</header>
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<RouterView />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
7
src/env.d.ts
vendored
Normal file
7
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<object, object, unknown>;
|
||||
export default component;
|
||||
}
|
||||
23
src/lib/utils.ts
Normal file
23
src/lib/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return "0 B";
|
||||
const k = 1024;
|
||||
const sizes = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${(bytes / Math.pow(k, i)).toFixed(i === 0 ? 0 : 2)} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
export function formatRate(bytesPerSec: number): string {
|
||||
return `${formatBytes(bytesPerSec)}/s`;
|
||||
}
|
||||
|
||||
export function shortenIpv6(addr: string): string {
|
||||
if (addr.length <= 24) return addr;
|
||||
return `${addr.slice(0, 10)}…${addr.slice(-8)}`;
|
||||
}
|
||||
10
src/main.ts
Normal file
10
src/main.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import App from "./App.vue";
|
||||
import { router } from "./router";
|
||||
import "./style.css";
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
app.mount("#app");
|
||||
49
src/router/index.ts
Normal file
49
src/router/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { createRouter, createWebHashHistory, type RouteRecordRaw } from "vue-router";
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: "/",
|
||||
redirect: "/status",
|
||||
},
|
||||
{
|
||||
path: "/status",
|
||||
name: "status",
|
||||
component: () => import("@/views/Status.vue"),
|
||||
meta: { title: "Status" },
|
||||
},
|
||||
{
|
||||
path: "/peers",
|
||||
name: "peers",
|
||||
component: () => import("@/views/Peers.vue"),
|
||||
meta: { title: "Peers" },
|
||||
},
|
||||
{
|
||||
path: "/routes",
|
||||
name: "routes",
|
||||
component: () => import("@/views/Routes.vue"),
|
||||
meta: { title: "Routes" },
|
||||
},
|
||||
{
|
||||
path: "/messages",
|
||||
name: "messages",
|
||||
component: () => import("@/views/Messages.vue"),
|
||||
meta: { title: "Messages" },
|
||||
},
|
||||
{
|
||||
path: "/topics",
|
||||
name: "topics",
|
||||
component: () => import("@/views/Topics.vue"),
|
||||
meta: { title: "Topics" },
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
name: "settings",
|
||||
component: () => import("@/views/Settings.vue"),
|
||||
meta: { title: "Settings" },
|
||||
},
|
||||
];
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
57
src/style.css
Normal file
57
src/style.css
Normal file
@@ -0,0 +1,57 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial,
|
||||
sans-serif;
|
||||
}
|
||||
}
|
||||
5
src/views/Messages.vue
Normal file
5
src/views/Messages.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<p class="text-sm text-muted-foreground">Messages view — wired in P4.</p>
|
||||
</template>
|
||||
5
src/views/Peers.vue
Normal file
5
src/views/Peers.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<p class="text-sm text-muted-foreground">Peers view — wired in P2.</p>
|
||||
</template>
|
||||
5
src/views/Routes.vue
Normal file
5
src/views/Routes.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<p class="text-sm text-muted-foreground">Routes view — wired in P3.</p>
|
||||
</template>
|
||||
5
src/views/Settings.vue
Normal file
5
src/views/Settings.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<p class="text-sm text-muted-foreground">Settings view — wired in P5.</p>
|
||||
</template>
|
||||
9
src/views/Status.vue
Normal file
9
src/views/Status.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Daemon status will appear here once the sidecar is wired up (P1).
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
5
src/views/Topics.vue
Normal file
5
src/views/Topics.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<p class="text-sm text-muted-foreground">Topics view — wired in P4.</p>
|
||||
</template>
|
||||
Reference in New Issue
Block a user