mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-07 10:32:49 +00:00
feat: tauri serial polyfill
This commit is contained in:
1
.github/workflows/publish.yml
vendored
1
.github/workflows/publish.yml
vendored
@@ -45,6 +45,7 @@ jobs:
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
with:
|
||||
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version
|
||||
releaseName: 'App v__VERSION__'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/typesafe-i18n@5.25.1/schema/typesafe-i18n.json",
|
||||
"$schema": "https://unpkg.com/typesafe-i18n@5.26.0/schema/typesafe-i18n.json",
|
||||
"baseLocale": "en",
|
||||
"adapter": "svelte"
|
||||
}
|
||||
818
package-lock.json
generated
818
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amacc1ng",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -26,13 +26,13 @@
|
||||
"@codemirror/lang-javascript": "^6.1.9",
|
||||
"@codemirror/language": "^6.8.0",
|
||||
"@codemirror/state": "^6.2.1",
|
||||
"@fontsource-variable/material-symbols-rounded": "^5.0.4",
|
||||
"@fontsource-variable/noto-sans-mono": "^5.0.4",
|
||||
"@fontsource-variable/material-symbols-rounded": "^5.0.6",
|
||||
"@fontsource-variable/noto-sans-mono": "^5.0.7",
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
"@modyfi/vite-plugin-yaml": "^1.0.4",
|
||||
"@sveltejs/adapter-static": "^2.0.2",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.4.2",
|
||||
"@sveltejs/adapter-static": "^2.0.3",
|
||||
"@sveltejs/kit": "^1.22.4",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.4.3",
|
||||
"@tauri-apps/api": "^1.4.0",
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@theaninova/prettier-config": "^1.0.0",
|
||||
@@ -42,33 +42,33 @@
|
||||
"@vite-pwa/sveltekit": "^0.2.5",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"codemirror": "^6.0.1",
|
||||
"cypress": "^12.17.1",
|
||||
"cypress": "^12.17.3",
|
||||
"flexsearch": "^0.7.31",
|
||||
"fontkit": "^2.0.2",
|
||||
"glob": "^10.3.1",
|
||||
"glob": "^10.3.3",
|
||||
"jsdom": "^22.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"sass": "^1.63.6",
|
||||
"stylelint": "^15.9.0",
|
||||
"prettier": "^3.0.1",
|
||||
"prettier-plugin-svelte": "^3.0.3",
|
||||
"sass": "^1.64.2",
|
||||
"stylelint": "^15.10.2",
|
||||
"stylelint-config-clean-order": "^5.0.1",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-prettier-scss": "^1.0.0",
|
||||
"stylelint-config-recommended-scss": "^12.0.0",
|
||||
"stylelint-config-standard-scss": "^10.0.0",
|
||||
"svelte": "^4.0.0",
|
||||
"svelte-check": "^3.4.3",
|
||||
"svelte": "^4.1.2",
|
||||
"svelte-check": "^3.4.6",
|
||||
"svelte-preprocess": "^5.0.4",
|
||||
"tippy.js": "^6.3.7",
|
||||
"ts-node": "^10.9.1",
|
||||
"typesafe-i18n": "^5.25.1",
|
||||
"typesafe-i18n": "^5.26.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.3.6",
|
||||
"vite": "^4.4.8",
|
||||
"vite-plugin-mkcert": "^1.16.0",
|
||||
"vite-plugin-pwa": "^0.16.4",
|
||||
"vitest": "^0.33.0"
|
||||
"vitest": "^0.34.1"
|
||||
},
|
||||
"type": "module",
|
||||
"prettier": "@theaninova/prettier-config"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["Thea Schöbl <dev@theaninova.de>"]
|
||||
license = "AGPL-3"
|
||||
|
||||
@@ -1,39 +1,99 @@
|
||||
use serde::Serialize;
|
||||
use serialport::{available_ports, SerialPortType};
|
||||
use serialport::{available_ports, SerialPort, SerialPortType};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::plugin::{Builder, TauriPlugin};
|
||||
use tauri::{command, generate_handler, Runtime};
|
||||
use tauri::{command, generate_handler, Manager, Runtime, State};
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("serial")
|
||||
.invoke_handler(generate_handler![get_serial_ports])
|
||||
.invoke_handler(generate_handler![
|
||||
get_serial_ports,
|
||||
open,
|
||||
close,
|
||||
read,
|
||||
write
|
||||
])
|
||||
.setup(move |app_handle| {
|
||||
app_handle.manage(SerialState::default());
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SerialState {
|
||||
handles: Arc<Mutex<HashMap<String, Box<dyn SerialPort>>>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WebSerialPortInfo {
|
||||
pub name: String,
|
||||
pub product_id: u16,
|
||||
pub vendor_id: u16,
|
||||
pub usb_product_id: u16,
|
||||
pub usb_vendor_id: u16,
|
||||
pub serial_number: Option<String>,
|
||||
pub manufacturer: Option<String>,
|
||||
pub product: Option<String>,
|
||||
}
|
||||
|
||||
#[command]
|
||||
fn get_serial_ports() -> Vec<WebSerialPortInfo> {
|
||||
available_ports()
|
||||
.unwrap()
|
||||
fn get_serial_ports() -> Result<Vec<WebSerialPortInfo>, String> {
|
||||
Ok(available_ports()
|
||||
.map_err(|err| err.to_string())?
|
||||
.iter()
|
||||
.filter_map(|port| match &port.port_type {
|
||||
SerialPortType::UsbPort(usb) => Some(WebSerialPortInfo {
|
||||
name: port.port_name.clone(),
|
||||
vendor_id: usb.vid,
|
||||
product_id: usb.pid,
|
||||
usb_vendor_id: usb.vid,
|
||||
usb_product_id: usb.pid,
|
||||
serial_number: usb.serial_number.clone(),
|
||||
manufacturer: usb.manufacturer.clone(),
|
||||
product: usb.product.clone(),
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[command]
|
||||
fn open(state: State<'_, SerialState>, path: String, baud_rate: u32) -> Result<(), String> {
|
||||
let mut handles = state.handles.lock().map_err(|err| err.to_string())?;
|
||||
if handles.contains_key(&path) {
|
||||
return Ok(());
|
||||
}
|
||||
let port = serialport::new(path.clone(), baud_rate)
|
||||
.open()
|
||||
.map_err(|err| err.to_string())?;
|
||||
handles.insert(path, port);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
fn close(state: State<'_, SerialState>, path: String) -> Result<(), String> {
|
||||
let mut handles = state.handles.lock().map_err(|err| err.to_string())?;
|
||||
handles.remove(&path).ok_or("Port is already closed")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
fn read(state: State<'_, SerialState>, path: String) -> Result<Vec<u8>, String> {
|
||||
let mut handles = state.handles.lock().map_err(|err| err.to_string())?;
|
||||
let port = handles.get_mut(&path).ok_or("Read: Port is not open")?;
|
||||
|
||||
let size = port.bytes_to_read().map_err(|err| err.to_string())?;
|
||||
let mut buffer: Vec<u8> = vec![0; size as usize];
|
||||
port.read_exact(buffer.as_mut_slice())
|
||||
.map_err(|err| err.to_string())?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
#[command]
|
||||
fn write(state: State<'_, SerialState>, path: String, chunk: Vec<u8>) -> Result<(), String> {
|
||||
let mut handles = state.handles.lock().map_err(|err| err.to_string())?;
|
||||
let port = handles.get_mut(&path).ok_or("Write: Port is not open")?;
|
||||
port.write_all(&chunk).map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "amacc1ng",
|
||||
"version": "0.1.0"
|
||||
"version": "0.4.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
@@ -51,7 +51,12 @@
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
"active": true,
|
||||
"endpoints": [
|
||||
"http://v2202207178592194230.supersrv.de:9216/update?current_version={{current_version}}&target={{target}}&arch={{arch}}"
|
||||
],
|
||||
"dialog": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDU5QjEwMEY5RjNBRjM4MEIKUldRTE9LL3orUUN4V2FMWDZkc2l2VUdOL3FSdUMwTk1ualNac095RVZXVEpqUEtORkFsWGZaTmsK"
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
export let results: number[] = []
|
||||
|
||||
export let width: number
|
||||
|
||||
console.log(width)
|
||||
</script>
|
||||
|
||||
<div class="list" style="width: {width}px">
|
||||
|
||||
@@ -8,7 +8,6 @@ export const popup: Action<HTMLButtonElement, ComponentType> = (node, Component)
|
||||
const edit = tippy(node, {
|
||||
interactive: true,
|
||||
trigger: "click",
|
||||
sticky: true,
|
||||
onShow(instance) {
|
||||
target = instance.popper.querySelector(".tippy-content") as HTMLElement
|
||||
target.classList.add("active")
|
||||
|
||||
23
src/lib/serial/TauriSerialDialog.svelte
Normal file
23
src/lib/serial/TauriSerialDialog.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import {createEventDispatcher} from "svelte"
|
||||
|
||||
export let ports: SerialPort[]
|
||||
const dispatch = createEventDispatcher<{confirm: SerialPort | undefined}>()
|
||||
let selected = ports[0].getInfo().name
|
||||
</script>
|
||||
|
||||
<dialog>
|
||||
{#each ports as port}
|
||||
{@const info = port.getInfo()}
|
||||
<label>{info.product}<input type="radio" name="port" value={info.name} bind:group={selected} /></label>
|
||||
{/each}
|
||||
|
||||
<button on:click={() => dispatch("confirm", undefined)}>Cancel</button>
|
||||
<button
|
||||
on:click={() =>
|
||||
dispatch(
|
||||
"confirm",
|
||||
ports.find(it => it.getInfo().name === selected),
|
||||
)}>Ok</button
|
||||
>
|
||||
</dialog>
|
||||
@@ -6,7 +6,7 @@ import type {CharaLayout} from "$lib/serialization/layout"
|
||||
import {persistentWritable} from "$lib/storage"
|
||||
import {userPreferences} from "$lib/preferences"
|
||||
|
||||
export const serialPort = writable<CharaDevice>()
|
||||
export const serialPort = writable<CharaDevice | undefined>()
|
||||
|
||||
export interface SerialLogEntry {
|
||||
type: "input" | "output" | "system"
|
||||
|
||||
@@ -11,7 +11,6 @@ if (browser && import.meta.env.TAURI_FAMILY !== undefined) {
|
||||
}
|
||||
|
||||
export async function getViablePorts(): Promise<SerialPort[]> {
|
||||
console.log(await navigator.serial.getPorts().then(it => it.map(it => it.getInfo())))
|
||||
return navigator.serial.getPorts().then(ports => ports.filter(it => it.getInfo().usbVendorId === VENDOR_ID))
|
||||
}
|
||||
|
||||
|
||||
8
src/lib/serial/tauri-serial-extension.d.ts
vendored
Normal file
8
src/lib/serial/tauri-serial-extension.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/// <references types="@types/w3c-web-serial" />
|
||||
|
||||
interface SerialPortInfo {
|
||||
name?: string
|
||||
serialNumber?: string
|
||||
manufacturer?: string
|
||||
product?: string
|
||||
}
|
||||
@@ -1,22 +1,65 @@
|
||||
import {invoke} from "@tauri-apps/api"
|
||||
import TauriSerialDialog from "$lib/serial/TauriSerialDialog.svelte"
|
||||
|
||||
export type TauriSerialPort = Pick<
|
||||
SerialPort,
|
||||
"getInfo" | "open" | "close" | "readable" | "writable" | "forget"
|
||||
>
|
||||
|
||||
function NativeSerialPort(info: SerialPortInfo): TauriSerialPort {
|
||||
return {
|
||||
getInfo() {
|
||||
return info
|
||||
},
|
||||
async open({baudRate}: SerialOptions) {
|
||||
await invoke("plugin:serial|open", {path: info.name, baudRate})
|
||||
},
|
||||
async close() {
|
||||
await invoke("plugin:serial|close", {path: info.name})
|
||||
},
|
||||
async forget() {
|
||||
// noop
|
||||
},
|
||||
readable: new ReadableStream({
|
||||
async pull(controller) {
|
||||
const result = await invoke<number[]>("plugin:serial|read", {path: info.name})
|
||||
controller.enqueue(new Uint8Array(result))
|
||||
},
|
||||
}),
|
||||
writable: new WritableStream({
|
||||
async write(chunk) {
|
||||
await invoke("plugin:serial|write", {path: info.name, chunk: Array.from(chunk)})
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error polyfill
|
||||
// noinspection JSConstantReassignment
|
||||
navigator.serial = {
|
||||
getPorts(): Promise<SerialPort[]> {
|
||||
async getPorts(): Promise<SerialPort[]> {
|
||||
return invoke<any[]>("plugin:serial|get_serial_ports").then(ports =>
|
||||
ports.map<Partial<SerialPort>>(port => ({
|
||||
getInfo() {
|
||||
return {
|
||||
name: port["name"],
|
||||
usbVendorId: port["vendor_id"],
|
||||
usbProductId: port["product_id"],
|
||||
serialNumber: port["serial_number"],
|
||||
manufacturer: port["manufacturer"],
|
||||
product: port["product"],
|
||||
} as SerialPortInfo
|
||||
},
|
||||
})),
|
||||
ports.map(NativeSerialPort),
|
||||
) as Promise<SerialPort[]>
|
||||
},
|
||||
async requestPort(options?: SerialPortRequestOptions): Promise<SerialPort> {
|
||||
const ports = await navigator.serial.getPorts().then(ports =>
|
||||
options?.filters !== undefined
|
||||
? ports.filter(port =>
|
||||
options.filters!.some(({usbVendorId, usbProductId}) => {
|
||||
const info = port.getInfo()
|
||||
return (
|
||||
(usbVendorId === undefined || info.usbVendorId === usbVendorId) &&
|
||||
(usbProductId === undefined || info.usbProductId === usbProductId)
|
||||
)
|
||||
}),
|
||||
)
|
||||
: ports,
|
||||
)
|
||||
|
||||
const dialog = new TauriSerialDialog({target: document.body, props: {ports}})
|
||||
const port = await new Promise<SerialPort>(resolve => dialog.$on("confirm", resolve))
|
||||
dialog.$destroy()
|
||||
return port
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
import Navigation from "./Navigation.svelte"
|
||||
import {canAutoConnect} from "$lib/serial/device"
|
||||
import {initSerial} from "$lib/serial/connection"
|
||||
import {pwaInfo} from "virtual:pwa-info"
|
||||
import type {LayoutServerData} from "./$types"
|
||||
import type {RegisterSWOptions} from "vite-plugin-pwa/types"
|
||||
import {browser} from "$app/environment"
|
||||
import BrowserWarning from "./BrowserWarning.svelte"
|
||||
import "tippy.js/animations/shift-away.css"
|
||||
@@ -46,21 +44,15 @@
|
||||
const dark = it.mode === "dark" // window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
applyTheme(theme, {target: document.body, dark})
|
||||
})
|
||||
|
||||
if (pwaInfo) {
|
||||
const {registerSW} = await import("virtual:pwa-register")
|
||||
registerSW({
|
||||
immediate: true,
|
||||
onRegisterError(error) {
|
||||
console.log("ServiceWorker Registration Error", error)
|
||||
},
|
||||
} satisfies RegisterSWOptions)
|
||||
if (import.meta.env.TAURI_FAMILY === undefined) {
|
||||
const {initPwa} = await import("./pwa-setup")
|
||||
await initPwa()
|
||||
}
|
||||
|
||||
if (browser && $userPreferences.autoConnect && (await canAutoConnect())) await initSerial()
|
||||
})
|
||||
|
||||
$: webManifestLink = pwaInfo ? pwaInfo.webManifest.linkTag : ""
|
||||
let webManifestLink = ""
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -76,7 +68,7 @@
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
{#if !import.meta.env.TAURI_FAMILY && browser && !("serial" in navigator)}
|
||||
{#if import.meta.env.TAURI_FAMILY === undefined && browser && !("serial" in navigator)}
|
||||
<BrowserWarning />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<button
|
||||
class="secondary"
|
||||
on:click={() => {
|
||||
$serialPort.forget()
|
||||
$serialPort?.forget()
|
||||
$serialPort = undefined
|
||||
}}><span class="icon">usb_off</span>{$LL.deviceManager.DISCONNECT()}</button
|
||||
>
|
||||
@@ -45,7 +45,7 @@
|
||||
href="/terminal"
|
||||
title={$LL.deviceManager.TERMINAL()}
|
||||
class="icon"
|
||||
disabled={$serialPort === undefined}
|
||||
class:disabled={$serialPort === undefined}
|
||||
on:click={() => (terminal = !terminal)}>terminal</a
|
||||
>
|
||||
<button
|
||||
@@ -62,13 +62,13 @@
|
||||
<h3>{$LL.deviceManager.bootMenu.TITLE()}</h3>
|
||||
<button
|
||||
on:click={() => {
|
||||
$serialPort.reboot()
|
||||
$serialPort?.reboot()
|
||||
$serialPort = undefined
|
||||
}}><span class="icon">restart_alt</span>{$LL.deviceManager.bootMenu.REBOOT()}</button
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
$serialPort.bootloader()
|
||||
$serialPort?.bootloader()
|
||||
$serialPort = undefined
|
||||
}}><span class="icon">rule_settings</span>{$LL.deviceManager.bootMenu.BOOTLOADER()}</button
|
||||
>
|
||||
@@ -176,31 +176,22 @@
|
||||
|
||||
transition: all 250ms ease;
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.icon {
|
||||
aspect-ratio: 1;
|
||||
padding-inline-end: 8px;
|
||||
font-size: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
color: var(--md-sys-color-on-secondary);
|
||||
background: var(--md-sys-color-secondary);
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: var(--md-sys-color-on-error);
|
||||
background: var(--md-sys-color-error);
|
||||
a.disabled,
|
||||
button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
button:active:not(:disabled) {
|
||||
color: var(--md-sys-color-on-surface-variant);
|
||||
background: var(--md-sys-color-surface-variant);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -47,9 +47,11 @@
|
||||
<button transition:fly={{x: -8}} class="icon" on:click={triggerShare}>share</button>
|
||||
<div transition:slide class="separator" />
|
||||
{/if}
|
||||
{#if import.meta.env.TAURI_FAMILY === undefined}
|
||||
{#await import("$lib/components/PwaStatus.svelte") then { default: PwaStatus }}
|
||||
<PwaStatus />
|
||||
{/await}
|
||||
{/if}
|
||||
{#if $serialPort}
|
||||
<button title={$LL.backup.TITLE()} use:popup={BackupPopup} class="icon {$syncStatus}">
|
||||
{#if $syncStatus === "downloading"}
|
||||
|
||||
16
src/routes/pwa-setup.ts
Normal file
16
src/routes/pwa-setup.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type {RegisterSWOptions} from "vite-plugin-pwa/types"
|
||||
|
||||
export async function initPwa(): Promise<string> {
|
||||
// @ts-expect-error confused TS
|
||||
const {pwaInfo} = await import("virtual:pwa-info")
|
||||
// @ts-expect-error confused TS
|
||||
const {registerSW} = await import("virtual:pwa-register")
|
||||
registerSW({
|
||||
immediate: true,
|
||||
onRegisterError(error) {
|
||||
console.log("ServiceWorker Registration Error", error)
|
||||
},
|
||||
} satisfies RegisterSWOptions)
|
||||
|
||||
return pwaInfo ? pwaInfo.webManifest.linkTag : ""
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Google Chrome</title><path d="M12 0C8.21 0 4.831 1.757 2.632 4.501l3.953 6.848A5.454 5.454 0 0 1 12 6.545h10.691A12 12 0 0 0 12 0zM1.931 5.47A11.943 11.943 0 0 0 0 12c0 6.012 4.42 10.991 10.189 11.864l3.953-6.847a5.45 5.45 0 0 1-6.865-2.29zm13.342 2.166a5.446 5.446 0 0 1 1.45 7.09l.002.001h-.002l-5.344 9.257c.206.01.413.016.621.016 6.627 0 12-5.373 12-12 0-1.54-.29-3.011-.818-4.364zM12 16.364a4.364 4.364 0 1 1 0-8.728 4.364 4.364 0 0 1 0 8.728Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 535 B |
@@ -1 +0,0 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Microsoft Edge</title><path d="M21.86 17.86q.14 0 .25.12.1.13.1.25t-.11.33l-.32.46-.43.53-.44.5q-.21.25-.38.42l-.22.23q-.58.53-1.34 1.04-.76.51-1.6.91-.86.4-1.74.64t-1.67.24q-.9 0-1.69-.28-.8-.28-1.48-.78-.68-.5-1.22-1.17-.53-.66-.92-1.44-.38-.77-.58-1.6-.2-.83-.2-1.67 0-1 .32-1.96.33-.97.87-1.8.14.95.55 1.77.41.82 1.02 1.5.6.68 1.38 1.21.78.54 1.64.9.86.36 1.77.56.92.2 1.8.2 1.12 0 2.18-.24 1.06-.23 2.06-.72l.2-.1.2-.05zm-15.5-1.27q0 1.1.27 2.15.27 1.06.78 2.03.51.96 1.24 1.77.74.82 1.66 1.4-1.47-.2-2.8-.74-1.33-.55-2.48-1.37-1.15-.83-2.08-1.9-.92-1.07-1.58-2.33T.36 14.94Q0 13.54 0 12.06q0-.81.32-1.49.31-.68.83-1.23.53-.55 1.2-.96.66-.4 1.35-.66.74-.27 1.5-.39.78-.12 1.55-.12.7 0 1.42.1.72.12 1.4.35.68.23 1.32.57.63.35 1.16.83-.35 0-.7.07-.33.07-.65.23v-.02q-.63.28-1.2.74-.57.46-1.05 1.04-.48.58-.87 1.26-.38.67-.65 1.39-.27.71-.42 1.44-.15.72-.15 1.38zM11.96.06q1.7 0 3.33.39 1.63.38 3.07 1.15 1.43.77 2.62 1.93 1.18 1.16 1.98 2.7.49.94.76 1.96.28 1 .28 2.08 0 .89-.23 1.7-.24.8-.69 1.48-.45.68-1.1 1.22-.64.53-1.45.88-.54.24-1.11.36-.58.13-1.16.13-.42 0-.97-.03-.54-.03-1.1-.12-.55-.1-1.05-.28-.5-.19-.84-.5-.12-.09-.23-.24-.1-.16-.1-.33 0-.15.16-.35.16-.2.35-.5.2-.28.36-.68.16-.4.16-.95 0-1.06-.4-1.96-.4-.91-1.06-1.64-.66-.74-1.52-1.28-.86-.55-1.79-.89-.84-.3-1.72-.44-.87-.14-1.76-.14-1.55 0-3.06.45T.94 7.55q.71-1.74 1.81-3.13 1.1-1.38 2.52-2.35Q6.68 1.1 8.37.58q1.7-.52 3.58-.52Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Opera</title><path d="M8.051 5.238c-1.328 1.566-2.186 3.883-2.246 6.48v.564c.061 2.598.918 4.912 2.246 6.479 1.721 2.236 4.279 3.654 7.139 3.654 1.756 0 3.4-.537 4.807-1.471C17.879 22.846 15.074 24 12 24c-.192 0-.383-.004-.57-.014C5.064 23.689 0 18.436 0 12 0 5.371 5.373 0 12 0h.045c3.055.012 5.84 1.166 7.953 3.055-1.408-.93-3.051-1.471-4.81-1.471-2.858 0-5.417 1.42-7.14 3.654h.003zM24 12c0 3.556-1.545 6.748-4.002 8.945-3.078 1.5-5.946.451-6.896-.205 3.023-.664 5.307-4.32 5.307-8.74 0-4.422-2.283-8.075-5.307-8.74.949-.654 3.818-1.703 6.896-.205C22.455 5.25 24 8.445 24 12z"/></svg>
|
||||
|
Before Width: | Height: | Size: 665 B |
@@ -1 +0,0 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Vivaldi</title><path d="M12 0C6.75 0 3.817 0 1.912 1.904.007 3.81 0 6.75 0 12s0 8.175 1.912 10.08C3.825 23.985 6.75 24 12 24c5.25 0 8.183 0 10.088-1.904C23.993 20.19 24 17.25 24 12s0-8.175-1.912-10.08C20.175.015 17.25 0 12 0zm-.168 3a9 9 0 016.49 2.648 9 9 0 010 12.704A9 9 0 1111.832 3zM7.568 7.496a1.433 1.433 0 00-.142.004A1.5 1.5 0 006.21 9.75l1.701 3c.93 1.582 1.839 3.202 2.791 4.822a1.417 1.417 0 001.41.75 1.5 1.5 0 001.223-.81l4.447-7.762A1.56 1.56 0 0018 8.768a1.5 1.5 0 10-2.828.914 2.513 2.513 0 01.256 1.119v.246a2.393 2.393 0 01-2.52 2.13 2.348 2.348 0 01-1.965-1.214c-.307-.51-.6-1.035-.9-1.553-.42-.72-.826-1.41-1.246-2.16a1.433 1.433 0 00-1.229-.754Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 754 B |
@@ -6,6 +6,7 @@ import {SvelteKitPWA} from "@vite-pwa/sveltekit"
|
||||
import ViteYaml from "@modyfi/vite-plugin-yaml"
|
||||
|
||||
const isTauri = "TAURI_FAMILY" in process.env
|
||||
console.info(isTauri ? "Building for Tauri" : "Building for PWA")
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
@@ -17,7 +18,8 @@ export default defineConfig({
|
||||
ViteYaml(),
|
||||
sveltekit(),
|
||||
...(isTauri
|
||||
? [
|
||||
? []
|
||||
: [
|
||||
SvelteKitPWA({
|
||||
kit: {
|
||||
trailingSlash: "always",
|
||||
@@ -41,7 +43,6 @@ export default defineConfig({
|
||||
],
|
||||
},
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
]),
|
||||
],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user