Add CUTOS capability and core API examples
This commit is contained in:
@@ -0,0 +1 @@
|
||||
VITE_CUTOS_BROKER_URL=mock
|
||||
@@ -0,0 +1 @@
|
||||
VITE_CUTOS_BROKER_URL=localhost
|
||||
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
release
|
||||
*.local
|
||||
*.log
|
||||
.DS_Store
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"printWidth": 200,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none",
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# CUTOS Receipt Printer Demo
|
||||
|
||||
End-to-end LWA for `@cutos/device-receipt-printer` and the MS-MA90 Provider.
|
||||
|
||||
The Demo covers USB/serial connection, device information, styled text, QR code, paper feed, cutting, status, and Device events. Text and QR printing explicitly call `feed({ dots: 240 })` after printing so the result advances to the paper outlet. The standalone Feed action also defaults to 240 dots. Development uses CUTOS Mock through `.env.development`; deployed builds use CUTOS Runtime and the declared Provider dependency.
|
||||
|
||||
The card grid is responsive: portrait displays use two columns, landscape displays use four columns, and narrow displays use one column.
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npx cutos lwa build
|
||||
npx cutos lwa upload
|
||||
npx cutos lwa publish --device <device-id>
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CUTOS LWA</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
+3470
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "demo-receipt-printer",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "cutos lwa build",
|
||||
"build:app": "vue-tsc --noEmit && vite build",
|
||||
"package": "cutos lwa package",
|
||||
"validate": "cutos lwa validate",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cutos/core": "^4.0.8",
|
||||
"@cutos/device-receipt-printer": "^4.0.0",
|
||||
"vue": "^3.4.31"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.40",
|
||||
"postcss-import": "^16.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"typescript": "^5.5.4",
|
||||
"unplugin-auto-import": "^0.18.0",
|
||||
"vite": "^5.3.4",
|
||||
"vue-tsc": "^2.0.26"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "demo-receipt-printer",
|
||||
"version": "0.1.3",
|
||||
"description": "CUTOS 4.0 Receipt Printer end-to-end demo",
|
||||
"params": {
|
||||
"title": "CUTOS Receipt Printer",
|
||||
"host": "localhost"
|
||||
},
|
||||
"drvDependencies": {
|
||||
"device-receipt-printer-provider-ms-ma90": "0.1.0"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,140 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { CoreAPI } from '@cutos/core'
|
||||
import {
|
||||
DeviceCapabilityReceiptPrinter,
|
||||
type DeviceInfo,
|
||||
type GetStatusResult
|
||||
} from '@cutos/device-receipt-printer'
|
||||
import { loadConfig } from '@/utils/config'
|
||||
import cutosLogo from '@/assets/cutos.png'
|
||||
|
||||
type EventKind = 'data' | 'status' | 'error'
|
||||
type Alignment = 'left' | 'center' | 'right'
|
||||
interface EventItem { id: number; time: string; kind: EventKind; value: unknown }
|
||||
interface ReceiptPrinterClient {
|
||||
init(): Promise<unknown>
|
||||
connect(params?: { connection?: 'usb' | 'serial'; port?: string; baudRate?: 9600 | 38400 | 115200 }): Promise<{ connected: boolean }>
|
||||
disconnect(): Promise<{ connected: boolean }>
|
||||
readDeviceInfo(): Promise<DeviceInfo>
|
||||
printText(params: { text: string; alignment?: Alignment; width?: number; height?: number; bold?: boolean }): Promise<{ success: boolean }>
|
||||
printQrCode(params: { content: string; size?: number; leftMargin?: number }): Promise<{ success: boolean }>
|
||||
feed(params?: { dots?: number }): Promise<{ success: boolean }>
|
||||
cut(params?: { mode?: 'full' | 'partial' }): Promise<{ success: boolean }>
|
||||
getStatus(): Promise<GetStatusResult>
|
||||
onData(fn: (value: unknown) => void): () => void
|
||||
onStatus(fn: (value: unknown) => void): () => void
|
||||
onError(fn: (value: unknown) => void): () => void
|
||||
dispose(): boolean
|
||||
}
|
||||
|
||||
class MockReceiptPrinter implements ReceiptPrinterClient {
|
||||
private listeners = { data: new Set<(v: any) => void>(), status: new Set<(v: any) => void>(), error: new Set<(v: any) => void>() }
|
||||
async init() { return 'Mock receipt printer initialized' }
|
||||
async connect() { this.emit('status', { status: 'online', updatedAt: Date.now() }); return { connected: true } }
|
||||
async disconnect() { this.emit('status', { status: 'offline', updatedAt: Date.now() }); return { connected: false } }
|
||||
async readDeviceInfo() { return { manufacturer: 'MASUNG', model: 'MS-MA90 Mock', version: '0.1.0', deviceType: 'receipt-printer', sdkVersion: '2.2.2.10' } }
|
||||
async printText() { return this.complete('printText') }
|
||||
async printQrCode() { return this.complete('printQrCode') }
|
||||
async feed() { return this.complete('feed') }
|
||||
async cut() { return this.complete('cut') }
|
||||
async getStatus() { return { code: 0, state: 'ready' as const, ready: true, message: 'Printer is ready.' } }
|
||||
onData(fn: (v: unknown) => void) { return this.listen('data', fn) }
|
||||
onStatus(fn: (v: unknown) => void) { return this.listen('status', fn) }
|
||||
onError(fn: (v: unknown) => void) { return this.listen('error', fn) }
|
||||
dispose() { Object.values(this.listeners).forEach(value => value.clear()); return true }
|
||||
private async complete(operation: string) { this.emit('data', { type: 'operation-complete', operation, success: true, completedAt: Date.now() }); return { success: true } }
|
||||
private listen(kind: EventKind, fn: (v: any) => void) { this.listeners[kind].add(fn); return () => this.listeners[kind].delete(fn) }
|
||||
private emit(kind: EventKind, value: unknown) { this.listeners[kind].forEach(fn => fn(value)) }
|
||||
}
|
||||
|
||||
const title = ref('CUTOS Receipt Printer')
|
||||
const host = ref('localhost')
|
||||
const coreVersion = ref('')
|
||||
const lwaName = ref('')
|
||||
const lwaVersion = ref('')
|
||||
const runtimeConnected = ref(false)
|
||||
const initialized = ref(false)
|
||||
const connected = ref(false)
|
||||
const busy = ref('')
|
||||
const errorText = ref('')
|
||||
const deviceInfo = ref<DeviceInfo | null>(null)
|
||||
const result = ref<unknown>(null)
|
||||
const printerStatus = ref<GetStatusResult | null>(null)
|
||||
const connection = ref<'usb' | 'serial'>('usb')
|
||||
const port = ref('')
|
||||
const baudRate = ref<9600 | 38400 | 115200>(115200)
|
||||
const text = ref('CUTOS 4.0\nMS-MA90 receipt printer')
|
||||
const alignment = ref<Alignment>('left')
|
||||
const bold = ref(false)
|
||||
const width = ref(1)
|
||||
const height = ref(1)
|
||||
const qrContent = ref('https://www.cut-os.com')
|
||||
const qrSize = ref(6)
|
||||
const qrLeftMargin = ref(0)
|
||||
const feedDots = ref(240)
|
||||
const cutMode = ref<'full' | 'partial'>('partial')
|
||||
const events = ref<EventItem[]>([])
|
||||
const dataEvents = computed(() => events.value.filter(event => event.kind === 'data'))
|
||||
const statusEvents = computed(() => events.value.filter(event => event.kind === 'status'))
|
||||
const errorEvents = computed(() => events.value.filter(event => event.kind === 'error'))
|
||||
let device: ReceiptPrinterClient | null = null
|
||||
let unsubscribers: Array<() => void> = []
|
||||
|
||||
function format(value: unknown) { return value == null ? 'No data' : typeof value === 'string' ? value : JSON.stringify(value, null, 2) }
|
||||
function addEvent(kind: EventKind, value: unknown) { events.value.unshift({ id: Date.now() + Math.random(), time: new Date().toLocaleTimeString(), kind, value }); events.value = events.value.slice(0, 40) }
|
||||
async function run(name: string, action: () => Promise<void>) { busy.value = name; errorText.value = ''; try { await action() } catch (error) { errorText.value = error instanceof Error ? error.message : String(error); addEvent('error', errorText.value) } finally { busy.value = '' } }
|
||||
async function connect() { await run('connect', async () => { const params = connection.value === 'serial' ? { connection: connection.value, port: port.value, baudRate: baudRate.value } : { connection: connection.value, ...(port.value ? { port: port.value } : {}) }; connected.value = (await device!.connect(params)).connected; if (connected.value) { deviceInfo.value = await device!.readDeviceInfo(); printerStatus.value = await device!.getStatus() } }) }
|
||||
async function disconnect() { await run('disconnect', async () => { await device!.disconnect(); connected.value = false }) }
|
||||
async function printText() { await run('printText', async () => { const printed = await device!.printText({ text: text.value, alignment: alignment.value, bold: bold.value, width: width.value, height: height.value }); const fed = await device!.feed({ dots: 240 }); result.value = { printed, fed, feedDots: 240 } }) }
|
||||
async function printQrCode() { await run('printQrCode', async () => { const printed = await device!.printQrCode({ content: qrContent.value, size: qrSize.value, leftMargin: qrLeftMargin.value }); const fed = await device!.feed({ dots: 240 }); result.value = { printed, fed, feedDots: 240 } }) }
|
||||
async function feed() { await run('feed', async () => { result.value = await device!.feed({ dots: feedDots.value }) }) }
|
||||
async function cut() { await run('cut', async () => { result.value = await device!.cut({ mode: cutMode.value }) }) }
|
||||
async function refreshStatus() { await run('getStatus', async () => { printerStatus.value = await device!.getStatus() }) }
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const config = await loadConfig()
|
||||
title.value = String(config.params.title || title.value)
|
||||
lwaName.value = config.name
|
||||
lwaVersion.value = config.version
|
||||
host.value = import.meta.env.VITE_CUTOS_BROKER_URL || String(config.params.host || 'localhost')
|
||||
await CoreAPI.init(host.value)
|
||||
runtimeConnected.value = CoreAPI.connected()
|
||||
coreVersion.value = CoreAPI.getVersion()
|
||||
device = host.value === 'mock' ? new MockReceiptPrinter() : new DeviceCapabilityReceiptPrinter()
|
||||
await device.init()
|
||||
unsubscribers = [
|
||||
device.onData(value => addEvent('data', value)),
|
||||
device.onStatus(value => { addEvent('status', value); const state = value && typeof value === 'object' && 'status' in value ? value.status : ''; if (state === 'online') connected.value = true; if (state === 'offline') connected.value = false }),
|
||||
device.onError(value => addEvent('error', value))
|
||||
]
|
||||
initialized.value = true
|
||||
} catch (error) { errorText.value = error instanceof Error ? error.message : String(error) }
|
||||
})
|
||||
onUnmounted(() => { unsubscribers.forEach(unsubscribe => unsubscribe()); device?.dispose() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-shell">
|
||||
<header class="topbar">
|
||||
<div class="brand"><img :src="cutosLogo" alt="CUTOS"><div><h1>{{ title }}</h1><p>Device Capability · CUTOS 4.0</p></div></div>
|
||||
<div class="status-cluster"><span class="version-tag">Core {{ coreVersion || '—' }}</span><span class="version-tag">LWA {{ lwaName }} {{ lwaVersion || '—' }}</span><span class="connection" :class="{ online: runtimeConnected }"><i></i>{{ runtimeConnected ? 'Connected' : 'Disconnected' }}</span></div>
|
||||
</header>
|
||||
<main>
|
||||
<section v-if="errorText" class="error-banner"><strong>Operation failed</strong><span>{{ errorText }}</span></section>
|
||||
<section class="section-heading"><div><span class="eyebrow">{{ host }}</span><h2>Receipt Printer · MS-MA90</h2></div><div class="dependency-tags"><span class="provider-tag">Capability: <strong>@cutos/device-receipt-printer 4.0.0</strong></span><span class="provider-tag">Provider: <strong>device-receipt-printer-provider-ms-ma90 0.1.0</strong></span></div></section>
|
||||
<section class="result-grid">
|
||||
<article class="result-card"><div class="result-header method-header"><div><h3>Connection</h3><code>connect() / disconnect()</code></div><span class="state-tag" :class="{ online: connected }">{{ connected ? 'online' : 'offline' }}</span></div><div class="card-body form"><label>Connection<select v-model="connection"><option value="usb">USB</option><option value="serial">Serial</option></select></label><label>{{ connection === 'usb' ? 'USB port (optional)' : 'COM port' }}<input v-model="port" :placeholder="connection === 'usb' ? 'Auto detect' : 'COM1'"></label><label v-if="connection === 'serial'">Baud rate<select v-model="baudRate"><option :value="9600">9600</option><option :value="38400">38400</option><option :value="115200">115200</option></select></label><div class="button-row"><button class="button primary" :disabled="!initialized || !!busy || connected" @click="connect">Connect</button><button class="button secondary" :disabled="!connected || !!busy" @click="disconnect">Disconnect</button></div></div></article>
|
||||
<article class="result-card"><div class="result-header"><h3>Device information</h3><code>readDeviceInfo()</code></div><pre>{{ format(deviceInfo) }}</pre></article>
|
||||
<article class="result-card"><div class="result-header"><h3>Text</h3><code>printText()</code></div><div class="card-body form"><label>Text<textarea v-model="text" rows="3"></textarea></label><div class="inline-fields"><label>Align<select v-model="alignment"><option>left</option><option>center</option><option>right</option></select></label><label>Width<input v-model.number="width" type="number" min="1" max="8"></label><label>Height<input v-model.number="height" type="number" min="1" max="8"></label></div><label class="check"><input v-model="bold" type="checkbox">Bold</label><button class="button primary" :disabled="!connected || !!busy || !text" @click="printText">Print text</button></div></article>
|
||||
<article class="result-card"><div class="result-header"><h3>QR code</h3><code>printQrCode()</code></div><div class="card-body form"><label>Content<input v-model="qrContent"></label><div class="inline-fields"><label>Size<input v-model.number="qrSize" type="number" min="1" max="8"></label><label>Left margin (mm)<input v-model.number="qrLeftMargin" type="number" min="0" max="27"></label></div><button class="button primary" :disabled="!connected || !!busy || !qrContent" @click="printQrCode">Print QR code</button></div></article>
|
||||
<article class="result-card"><div class="result-header"><h3>Paper</h3><code>feed() / cut()</code></div><div class="card-body form"><label>Feed dots<input v-model.number="feedDots" type="number" min="0" max="250"></label><button class="button secondary" :disabled="!connected || !!busy" @click="feed">Feed paper</button><label>Cut mode<select v-model="cutMode"><option value="partial">Partial</option><option value="full">Full</option></select></label><button class="button primary" :disabled="!connected || !!busy" @click="cut">Cut paper</button></div></article>
|
||||
<article class="result-card"><div class="result-header"><h3>Printer status</h3><code>getStatus()</code></div><div class="card-body"><button class="button secondary" :disabled="!connected || !!busy" @click="refreshStatus">Refresh</button><pre>{{ format(printerStatus) }}</pre></div></article>
|
||||
<article class="result-card event-card"><div class="result-header"><h3>Operations</h3><code>onData()</code></div><div class="event-list"><div v-for="event in dataEvents" :key="event.id" class="event-row"><time>{{ event.time }}</time><pre>{{ format(event.value) }}</pre></div><p v-if="!dataEvents.length" class="empty-state">Waiting for operations…</p></div></article>
|
||||
<article class="result-card event-card"><div class="result-header"><h3>Status / Errors</h3><code>onStatus() / onError()</code></div><div class="event-list"><div v-for="event in [...statusEvents, ...errorEvents]" :key="event.id" class="event-row" :class="{ 'error-event': event.kind === 'error' }"><time>{{ event.time }}</time><pre>{{ format(event.value) }}</pre></div><p v-if="!statusEvents.length && !errorEvents.length" class="empty-state">Waiting for status…</p></div></article>
|
||||
</section>
|
||||
<section class="last-result"><strong>Last result</strong><pre>{{ format(result) }}</pre></section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
@@ -0,0 +1,10 @@
|
||||
@tailwind base; @tailwind components; @tailwind utilities;
|
||||
:root{font-family:Inter,"Segoe UI",Arial,sans-serif;color:#20262c;background:#eef1f3;font-synthesis:none}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh}button,input{font:inherit}button{cursor:pointer}button:disabled{cursor:not-allowed;opacity:.48}.app-shell{min-height:100vh;background:#eef1f3}
|
||||
.topbar{min-height:82px;padding:14px clamp(18px,4vw,52px);display:flex;align-items:center;justify-content:space-between;gap:24px;color:#f8fafb;background:#20262c;border-bottom:4px solid #16a394}.brand{display:flex;align-items:center;gap:15px}.brand img{width:42px;height:42px;object-fit:contain}.brand h1{margin:0;font-size:20px}.brand p{margin:4px 0 0;font-size:12px;color:#aeb8bf}.status-cluster,.dependency-tags,.button-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap}.version-tag,.connection{height:30px;display:inline-flex;align-items:center;padding:0 10px;font-size:12px;white-space:nowrap;border:1px solid #4b555d;border-radius:4px;background:#2b3339}.connection i{width:8px;height:8px;margin-right:7px;border-radius:50%;background:#e06b65}.connection.online i{background:#42c6a8}
|
||||
main{width:100%;padding:28px clamp(18px,2vw,36px) 32px}.error-banner{display:flex;gap:12px;margin-bottom:20px;padding:12px 16px;color:#7e2420;background:#fff0ef;border-left:4px solid #d54d47}.section-heading{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:15px}.section-heading h2{margin:3px 0 0;font-size:18px}.eyebrow{color:#64717a;font:11px "Cascadia Code",Consolas,monospace}.dependency-tags{justify-content:flex-end}.provider-tag{padding:6px 9px;color:#5d6870;background:#f8fafb;border:1px solid #cbd3d8;border-radius:4px;font:11px "Cascadia Code",Consolas,monospace}.provider-tag strong{color:#253038}
|
||||
.result-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.result-card{min-width:0;min-height:225px;background:#fff;border:1px solid #d8dee2;border-radius:6px;overflow:hidden}.result-header{min-height:66px;padding:13px 15px;border-bottom:1px solid #e4e8eb;background:#f8fafb}.result-header h3{margin:0 0 6px;font-size:14px}.result-header code{display:block;color:#68757d;font-size:11px;overflow-wrap:anywhere}.method-header{display:flex;align-items:center;justify-content:space-between;gap:14px}.state-tag{padding:5px 8px;color:#8b3c37;background:#fff0ef;border-radius:3px;font:10px "Cascadia Code",Consolas,monospace}.state-tag.online{color:#087b71;background:#e8f7f4}.card-body{padding:15px}.button{min-height:34px;padding:7px 13px;border:1px solid transparent;border-radius:4px;font-weight:650;font-size:12px}.button.primary{color:#fff;background:#138b80;border-color:#138b80}.button.secondary{color:#2d373d;background:#fff;border-color:#bfc8cd}
|
||||
pre{margin:0;padding:15px;font:12px/1.55 "Cascadia Code",Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;overflow:auto}.result-card>pre{height:210px}.card-body>pre{height:110px;margin-top:12px;padding:10px;background:#f7f9fa;border:1px solid #e0e5e8}.form{display:grid;gap:12px}.form label{display:grid;gap:6px;color:#5d6870;font-size:12px}.form input,.form select,.form textarea{width:100%;min-height:36px;padding:7px 9px;border:1px solid #bfc8cd;border-radius:4px;background:#fff}.form textarea{resize:vertical}.form .check{display:flex;align-items:center;grid-template-columns:auto 1fr}.form .check input{width:auto;min-height:auto}.inline-fields{display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px}.last-result{display:grid;grid-template-columns:auto minmax(0,1fr);gap:15px;align-items:start;margin-top:12px;padding:12px 15px;background:#fff;border:1px solid #d8dee2;border-radius:6px}.last-result strong{padding-top:4px;font-size:12px}.last-result pre{padding:4px 0}
|
||||
.event-card{height:360px;display:grid;grid-template-rows:auto minmax(0,1fr)}.event-list{min-height:0;overflow:auto}.event-row{display:grid;grid-template-columns:78px minmax(0,1fr);gap:10px;padding:8px 13px;border-bottom:1px solid #edf0f2}.event-row time{padding-top:4px;color:#77838b;font-size:11px}.event-row pre{padding:4px 0}.error-event pre{color:#b43b35}.empty-state{margin:0;padding:24px 15px;color:#77838b;font-size:12px}
|
||||
@media(orientation:landscape) and (min-width:1000px){.result-grid{grid-template-columns:repeat(4,minmax(0,1fr))}}
|
||||
@media(max-width:640px){.topbar,.section-heading{align-items:flex-start;flex-direction:column}.result-grid{grid-template-columns:1fr}.inline-fields{grid-template-columns:1fr}.last-result{grid-template-columns:1fr}}
|
||||
.file-name{color:#77838b;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
@@ -0,0 +1,66 @@
|
||||
export interface LwaConfig {
|
||||
name: string
|
||||
version: string
|
||||
description?: string
|
||||
params: {
|
||||
title: string
|
||||
host: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
drvDependencies?: Record<string, string>
|
||||
}
|
||||
|
||||
const defaultConfig: LwaConfig = {
|
||||
name: 'cutos-lwa',
|
||||
version: '0.0.0',
|
||||
params: {
|
||||
title: 'CUTOS LWA',
|
||||
host: 'localhost'
|
||||
}
|
||||
}
|
||||
|
||||
export let config: LwaConfig = { ...defaultConfig, params: { ...defaultConfig.params } }
|
||||
|
||||
function parse(str?: string) {
|
||||
if (!str || !str.length) return {}
|
||||
|
||||
return str.split('&').reduce<Record<string, string>>((acc, row) => {
|
||||
const [key, value] = row.split('=').map(decodeURIComponent)
|
||||
acc[key] = value
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
function mergeQueryParams(config: LwaConfig) {
|
||||
if (typeof window === 'undefined') {
|
||||
return config
|
||||
}
|
||||
|
||||
const query = location.href.split('?')[1]
|
||||
const { params } = parse(query) as { params?: string }
|
||||
|
||||
try {
|
||||
if (params) config.params = { ...config.params, ...JSON.parse(params) }
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
export async function loadConfig(): Promise<LwaConfig> {
|
||||
try {
|
||||
const configUrl =
|
||||
typeof window === 'undefined' ? 'config.json' : new URL('config.json', window.location.href).toString()
|
||||
const response = await fetch(configUrl, { cache: 'no-cache' })
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load config.json: ${response.status}`)
|
||||
}
|
||||
config = mergeQueryParams((await response.json()) as LwaConfig)
|
||||
return config
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
config = mergeQueryParams({ ...defaultConfig, params: { ...defaultConfig.params } })
|
||||
return config
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_CUTOS_BROKER_URL?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.vue"]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
publicDir: 'public',
|
||||
build: {
|
||||
target: ['chrome74']
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
// '/proxy': 'http://192.168.1.30'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ['@cutos/core'],
|
||||
alias: {
|
||||
'@': '/src'
|
||||
}
|
||||
},
|
||||
define: {},
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
include: [
|
||||
/\.[tj]sx?$/,
|
||||
/\.vue$/,
|
||||
/\.vue\?vue/,
|
||||
/\.md$/
|
||||
],
|
||||
imports: ['vue'],
|
||||
vueTemplate: true,
|
||||
cache: true,
|
||||
dts: false
|
||||
})
|
||||
],
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
define: {
|
||||
global: 'globalThis'
|
||||
},
|
||||
target: 'es2015',
|
||||
supported: {
|
||||
bigint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user