feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as a monorepo subdirectory under desktop/. Source only — node_modules, dist, Cargo target and the upstream .git history are excluded. desktop/ keeps its own independent Cargo workspace (desktop/src-tauri with crates/*); it is not part of the backend boosapi crate, so a `cargo check` at the metapi root still compiles only the backend. Build the desktop app from desktop/src-tauri per desktop/README.md. Note: app icons are still placeholder assets pending the final BoosAPI logo. The updater signing keys live outside the repo and are not included.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# Example: Monaco + Shiki theme sync
|
||||
|
||||
This minimal example shows how to keep Monaco editor themes and standalone Shiki-rendered code snippets in sync.
|
||||
|
||||
Goal
|
||||
|
||||
- Register (or pre-register) themes and languages for both Monaco and Shiki.
|
||||
- Optionally reuse the returned Shiki highlighter from `registerMonacoThemes` for rendering HTML snippets.
|
||||
|
||||
Files
|
||||
|
||||
- A simple Vue component (see snippet below) that:
|
||||
- calls `registerMonacoThemes` to preload themes & languages,
|
||||
- creates an editor and uses a preloaded Shiki highlighter for manual preview rendering,
|
||||
- switches theme via `setTheme` and also renders a Shiki HTML snippet using the returned highlighter.
|
||||
|
||||
Quick steps
|
||||
|
||||
1. Create a new Vite + Vue project (or use the existing `examples/streaming-demo` as base).
|
||||
2. Install package dependencies (you need `vue`, `monaco-editor`, `shiki`, and this library locally or from npm).
|
||||
3. Add the component below and run the dev server.
|
||||
|
||||
Running the example
|
||||
|
||||
- From the repository root (recommended with pnpm workspaces):
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm --filter ./examples/shiki-sync dev
|
||||
```
|
||||
|
||||
- Or inside the example directory:
|
||||
|
||||
```bash
|
||||
cd examples/shiki-sync
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Retry behavior
|
||||
|
||||
- The example UI shows a small "Retry" button if loading themes fails (e.g. network or build issue). Click it to re-attempt preloading themes & languages.
|
||||
|
||||
Vue component (single-file component)
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { registerMonacoThemes, useMonaco } from 'stream-monaco'
|
||||
|
||||
const container = ref<HTMLElement | null>(null)
|
||||
const preview = ref<HTMLDivElement | null>(null)
|
||||
|
||||
const allThemes = ['vitesse-dark', 'vitesse-light']
|
||||
const allLanguages = ['javascript']
|
||||
|
||||
// preload themes & languages and get a shiki highlighter
|
||||
let shikiHighlighter: any = null
|
||||
onMounted(async () => {
|
||||
shikiHighlighter = await registerMonacoThemes(allThemes, allLanguages)
|
||||
})
|
||||
|
||||
const { createEditor, setTheme } = useMonaco({
|
||||
themes: allThemes,
|
||||
languages: allLanguages,
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
if (container.value) {
|
||||
await createEditor(container.value, 'console.log("hello world")', 'javascript')
|
||||
}
|
||||
})
|
||||
|
||||
function applyShikiRender(themeName: string) {
|
||||
if (!shikiHighlighter || !preview.value)
|
||||
return
|
||||
try {
|
||||
const html = shikiHighlighter.codeToHtml('console.log("hello world")', { lang: 'javascript', theme: themeName })
|
||||
preview.value.innerHTML = html
|
||||
}
|
||||
catch (e) {
|
||||
// fallback: highlighter may not support codeToHtml in some builds; try setTheme instead
|
||||
try {
|
||||
shikiHighlighter.setTheme(themeName)
|
||||
// user should re-render their snippets accordingly
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
function switchToDark() {
|
||||
setTheme('vitesse-dark')
|
||||
applyShikiRender('vitesse-dark')
|
||||
}
|
||||
|
||||
function switchToLight() {
|
||||
setTheme('vitesse-light')
|
||||
applyShikiRender('vitesse-light')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="controls">
|
||||
<button @click="switchToDark">
|
||||
Dark
|
||||
</button>
|
||||
<button @click="switchToLight">
|
||||
Light
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ref="container" style="height:300px;border:1px solid #ddd;margin-top:12px" />
|
||||
|
||||
<h3>Shiki preview</h3>
|
||||
<div ref="preview" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.controls { display:flex; gap:8px }
|
||||
</style>
|
||||
```
|
||||
|
||||
Notes
|
||||
|
||||
- `registerMonacoThemes` now returns the Shiki highlighter Promise — you can reuse it to render static snippets or to call `setTheme` on the highlighter directly.
|
||||
If you need to keep standalone Shiki-rendered snippets in sync when changing the editor theme, call `registerMonacoThemes(...)` to obtain the Shiki highlighter and then call its `setTheme(...)` / `codeToHtml(...)` methods yourself. The example demonstrates preloading and manual usage.
|
||||
- If you preload a *full* set of themes & languages at app start, the library will reuse the same highlighter instance and avoid re-creating it on theme changes.
|
||||
|
||||
Troubleshooting
|
||||
|
||||
- If Shiki's `codeToHtml` isn't exposed on the highlighter build you use (some builds differ), you can still call `setTheme` on the highlighter and re-run your own rendering pipeline.
|
||||
- For large numbers of themes/languages, consider on-demand loading to reduce startup cost: preload the common ones and lazily call `registerMonacoThemes` for others.
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>shiki-sync demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "shiki-sync-demo",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --strictPort"
|
||||
},
|
||||
"dependencies": {
|
||||
"stream-monaco": "workspace:*",
|
||||
"vue": "^3.5.33"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<script setup lang="ts">
|
||||
import type { ShikiHighlighter } from '../../../src'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { registerMonacoThemes, useMonaco } from '../../../src'
|
||||
|
||||
const container = ref<HTMLElement | null>(null)
|
||||
const preview = ref<HTMLDivElement | null>(null)
|
||||
|
||||
const allThemes = [
|
||||
'andromeeda',
|
||||
'aurora-x',
|
||||
'ayu-dark',
|
||||
'catppuccin-frappe',
|
||||
'catppuccin-latte',
|
||||
'catppuccin-macchiato',
|
||||
'catppuccin-mocha',
|
||||
'dark-plus',
|
||||
'dracula',
|
||||
'dracula-soft',
|
||||
'everforest-dark',
|
||||
'everforest-light',
|
||||
'github-dark',
|
||||
'github-dark-default',
|
||||
'github-dark-dimmed',
|
||||
'github-dark-high-contrast',
|
||||
'github-light',
|
||||
'github-light-default',
|
||||
'github-light-high-contrast',
|
||||
'gruvbox-dark-hard',
|
||||
'gruvbox-dark-medium',
|
||||
'gruvbox-dark-soft',
|
||||
'gruvbox-light-hard',
|
||||
'gruvbox-light-medium',
|
||||
'gruvbox-light-soft',
|
||||
'houston',
|
||||
'kanagawa-dragon',
|
||||
'kanagawa-lotus',
|
||||
'kanagawa-wave',
|
||||
'laserwave',
|
||||
'light-plus',
|
||||
'material-theme',
|
||||
'material-theme-darker',
|
||||
'material-theme-lighter',
|
||||
'material-theme-ocean',
|
||||
'material-theme-palenight',
|
||||
'min-dark',
|
||||
'min-light',
|
||||
'monokai',
|
||||
'night-owl',
|
||||
'nord',
|
||||
'one-dark-pro',
|
||||
'one-light',
|
||||
'plastic',
|
||||
'poimandres',
|
||||
'red',
|
||||
'rose-pine',
|
||||
'rose-pine-dawn',
|
||||
'rose-pine-moon',
|
||||
'slack-dark',
|
||||
'slack-ochin',
|
||||
'snazzy-light',
|
||||
'solarized-dark',
|
||||
'solarized-light',
|
||||
'synthwave-84',
|
||||
'tokyo-night',
|
||||
'vesper',
|
||||
'vitesse-black',
|
||||
'vitesse-dark',
|
||||
'vitesse-light',
|
||||
]
|
||||
const allLanguages = ['javascript']
|
||||
|
||||
let shikiHighlighter: ShikiHighlighter | null = null
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const selectedTheme = ref<string>(allThemes[0])
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
shikiHighlighter = await registerMonacoThemes(allThemes, allLanguages)
|
||||
}
|
||||
catch (e: any) {
|
||||
error.value = e?.message || String(e)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
async function retryLoad() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
shikiHighlighter = await registerMonacoThemes(allThemes, allLanguages)
|
||||
}
|
||||
catch (e: any) {
|
||||
error.value = e?.message || String(e)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const { createEditor, setTheme } = useMonaco({
|
||||
themes: allThemes,
|
||||
languages: allLanguages,
|
||||
theme: selectedTheme.value,
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
if (container.value) {
|
||||
await createEditor(container.value, 'console.log("hello world")', 'javascript')
|
||||
// apply initial theme to preview once editor created
|
||||
applyShikiRender(selectedTheme.value)
|
||||
}
|
||||
})
|
||||
|
||||
function applyShikiRender(themeName: string) {
|
||||
if (!shikiHighlighter || !preview.value)
|
||||
return
|
||||
|
||||
try {
|
||||
const html = shikiHighlighter.codeToHtml('console.log("hello world")', { lang: 'javascript', theme: themeName })
|
||||
preview.value.innerHTML = html
|
||||
}
|
||||
catch {
|
||||
try {
|
||||
shikiHighlighter.setTheme(themeName)
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
function onThemeChange(theme: string) {
|
||||
selectedTheme.value = theme
|
||||
setTheme(theme)
|
||||
applyShikiRender(theme)
|
||||
}
|
||||
|
||||
function onThemeSelectChange(e: Event) {
|
||||
const el = e.target as HTMLSelectElement | null
|
||||
if (!el)
|
||||
return
|
||||
onThemeChange(el.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="padding:16px">
|
||||
<div class="controls">
|
||||
<label for="theme-select">Theme:</label>
|
||||
<select id="theme-select" v-model="selectedTheme" :disabled="loading || !!error" @change="onThemeSelectChange">
|
||||
<option v-for="t in allThemes" :key="t" :value="t">
|
||||
{{ t }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="loading" style="margin-left:8px">Loading themes...</span>
|
||||
<span v-if="error" style="margin-left:8px;color:crimson">Error: {{ error }}</span>
|
||||
<button v-if="error" style="margin-left:8px" @click="retryLoad">
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ref="container" style="height:300px;border:1px solid #ddd;margin-top:12px" />
|
||||
|
||||
<h3>Shiki preview</h3>
|
||||
<div ref="preview" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.controls { display:flex; gap:8px }
|
||||
</style>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import 'monaco-editor/min/vs/editor/editor.main.css'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"jsx": "preserve",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["vite/client"],
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
})
|
||||
Reference in New Issue
Block a user