diff --git a/ui/src/styles.css b/ui/src/styles.css index e112060..f13a14d 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -4,16 +4,55 @@ color: #c0caf5; } +#app { + min-height: 100vh; +} + .gnb-shell { - max-width: 1180px; + width: min(100% - 48px, 1380px); + max-width: none; margin: 0 auto; - padding: 24px; + padding: 24px 0 56px; +} + +.gnb-shell > .page-header, +.gnb-shell > .section { + width: 100%; +} + +.gnb-shell .page-header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 24px; +} + +.gnb-shell .page-header-actions, +.gnb-shell .toolbar > .toolbar-group:last-child { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 8px; +} + +.gnb-shell .btn { + white-space: nowrap; +} + +.gnb-shell .btn.with-icon { + min-width: 150px; +} + +.gnb-shell .tabs { + max-width: 100%; + overflow-x: auto; + padding-bottom: 2px; } .gnb-status { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; + gap: 48px; margin: 24px 0; } @@ -52,12 +91,26 @@ background: rgba(122, 162, 247, 0.16); } +.gnb-list > .gnb-row-button { + display: grid; + grid-template-columns: 1fr; + gap: 8px; + min-width: 0; + padding: 12px 15px; +} + +.gnb-list > .gnb-row-button strong, +.gnb-list > .gnb-row-button span { + min-width: 0; + overflow-wrap: anywhere; +} + .gnb-inventory, .gnb-docs, .gnb-changes, .gnb-git { display: grid; - grid-template-columns: minmax(220px, 320px) minmax(0, 1fr); + grid-template-columns: minmax(280px, 380px) minmax(0, 1fr); gap: 24px; align-items: start; } @@ -68,14 +121,14 @@ .gnb-detail-grid { display: grid; - grid-template-columns: minmax(220px, 320px) minmax(0, 1fr); + grid-template-columns: minmax(240px, 340px) minmax(0, 1fr); gap: 16px; align-items: start; } .gnb-json { min-height: 320px; - max-height: 620px; + max-height: 70vh; overflow: auto; margin: 0; padding: 16px; @@ -104,7 +157,7 @@ .gnb-markdown { min-height: 360px; - max-height: 680px; + max-height: 70vh; overflow: auto; margin: 0; padding: 16px; @@ -115,6 +168,7 @@ font-size: 14px; line-height: 1.6; white-space: pre-wrap; + overflow-wrap: normal; } .gnb-checkbox-row { @@ -182,7 +236,17 @@ @media (max-width: 760px) { .gnb-shell { - padding: 16px; + width: min(100% - 24px, 1380px); + padding: 16px 0 40px; + } + + .gnb-shell .page-header { + grid-template-columns: 1fr; + } + + .gnb-shell .page-header-actions, + .gnb-shell .toolbar > .toolbar-group:last-child { + justify-content: flex-start; } .gnb-status, diff --git a/ui/vite.config.js b/ui/vite.config.js index 2a6a185..40603c0 100644 --- a/ui/vite.config.js +++ b/ui/vite.config.js @@ -1,9 +1,48 @@ import { fileURLToPath, URL } from "node:url"; +import { createReadStream, existsSync, statSync } from "node:fs"; +import { extname, join, normalize } from "node:path"; import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; +const uiKitRoot = fileURLToPath(new URL("./node_modules/gnexus-ui-kit", import.meta.url)); +const uiKitFontsDir = join(uiKitRoot, "dist", "assets", "fonts"); + +function serveUiKitFont(request, response, next) { + if (!request.url) { + next(); + return; + } + + const requestedPath = normalize(decodeURIComponent(request.url.split("?", 1)[0])).replace( + /^(\.\.[/\\])+/, + "" + ); + const filePath = join(uiKitFontsDir, requestedPath); + if (!filePath.startsWith(uiKitFontsDir) || !existsSync(filePath) || !statSync(filePath).isFile()) { + next(); + return; + } + + const contentType = extname(filePath) === ".ttf" ? "font/ttf" : "application/octet-stream"; + response.setHeader("Content-Type", contentType); + response.setHeader("Cache-Control", "public, max-age=3600"); + createReadStream(filePath).pipe(response); +} + +function gnexusUiKitFontServer() { + return { + name: "gnexus-ui-kit-font-server", + configureServer(server) { + server.middlewares.use("/assets/fonts", serveUiKitFont); + }, + configurePreviewServer(server) { + server.middlewares.use("/assets/fonts", serveUiKitFont); + } + }; +} + export default defineConfig({ - plugins: [vue()], + plugins: [gnexusUiKitFontServer(), vue()], resolve: { alias: { vue: fileURLToPath(new URL("./node_modules/vue/dist/vue.esm-bundler.js", import.meta.url))