refactor(stage-tamagotchi): type-safe commands codegen poc (#258)
* refactor(stage-tamagotchi): type-safe commands codegen poc * chore: remove the useless comment * chore: [suggestion] remove comments Co-authored-by: Neko <neko@ayaka.moe> * chore: [suggestion] remove comments Co-authored-by: Neko <neko@ayaka.moe> * chore: [suggestion] remove comments Co-authored-by: Neko <neko@ayaka.moe> --------- Co-authored-by: Neko <neko@ayaka.moe>
This commit is contained in:
Generated
+82
@@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
@@ -198,6 +204,8 @@ dependencies = [
|
||||
"rubato",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"specta-typescript",
|
||||
"symphonia",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
@@ -208,6 +216,7 @@ dependencies = [
|
||||
"tauri-plugin-positioner",
|
||||
"tauri-plugin-prevent-default",
|
||||
"tauri-plugin-window-state",
|
||||
"tauri-specta",
|
||||
"tokenizers",
|
||||
"tokio",
|
||||
"url",
|
||||
@@ -5247,6 +5256,50 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta"
|
||||
version = "2.0.0-rc.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971"
|
||||
dependencies = [
|
||||
"paste",
|
||||
"specta-macros",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta-macros"
|
||||
version = "2.0.0-rc.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.103",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta-serde"
|
||||
version = "0.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77216504061374659e7245eac53d30c7b3e5fe64b88da97c753e7184b0781e63"
|
||||
dependencies = [
|
||||
"specta",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "specta-typescript"
|
||||
version = "0.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3220a0c365e51e248ac98eab5a6a32f544ff6f961906f09d3ee10903a4f52b2d"
|
||||
dependencies = [
|
||||
"specta",
|
||||
"specta-serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spm_precompiled"
|
||||
version = "0.1.4"
|
||||
@@ -5700,6 +5753,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"serialize-to-javascript",
|
||||
"specta",
|
||||
"swift-rs",
|
||||
"tauri-build",
|
||||
"tauri-macros",
|
||||
@@ -5960,6 +6014,34 @@ dependencies = [
|
||||
"wry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-specta"
|
||||
version = "2.0.0-rc.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b23c0132dd3cf6064e5cd919b82b3f47780e9280e7b5910babfe139829b76655"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"specta-typescript",
|
||||
"tauri",
|
||||
"tauri-specta-macros",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-specta-macros"
|
||||
version = "2.0.0-rc.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a4aa93823e07859546aa796b8a5d608190cd8037a3a5dce3eb63d491c34bda8"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.103",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "2.5.0"
|
||||
|
||||
@@ -51,6 +51,9 @@ tokenizers = "0.21.2"
|
||||
url = "2.5.4"
|
||||
tauri-plugin-window-state = "2.3.0"
|
||||
tauri-plugin-positioner = "2.3.0"
|
||||
specta = "=2.0.0-rc.22"
|
||||
specta-typescript = "0.0.9"
|
||||
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6.1"
|
||||
|
||||
@@ -9,6 +9,8 @@ use std::{sync::atomic::Ordering, time::Duration};
|
||||
use native_macos::{get_mouse_location, get_window_frame};
|
||||
#[cfg(target_os = "windows")]
|
||||
use native_windows::{get_mouse_location, get_window_frame};
|
||||
#[cfg(debug_assertions)]
|
||||
use specta_typescript::Typescript;
|
||||
use state::{WindowClickThroughState, set_pass_through_enabled};
|
||||
use tauri::{
|
||||
Emitter,
|
||||
@@ -89,34 +91,65 @@ async fn stop_monitor<R: Runtime>(window: tauri::Window<R>) -> Result<()> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn start_pass_through<R: Runtime>(window: tauri::Window<R>) -> Result<()> {
|
||||
set_pass_through_enabled(&window, true).unwrap_or_else(|e| {
|
||||
#[specta::specta]
|
||||
async fn start_pass_through<R: Runtime>(
|
||||
window: tauri::Window<R>
|
||||
) -> std::result::Result<(), String> {
|
||||
set_pass_through_enabled(&window, true).map_err(|e| {
|
||||
log::error!("Failed to enable click-through: {e}");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn stop_pass_through<R: Runtime>(window: tauri::Window<R>) -> Result<()> {
|
||||
set_pass_through_enabled(&window, false).unwrap_or_else(|e| {
|
||||
#[specta::specta]
|
||||
async fn stop_pass_through<R: Runtime>(
|
||||
window: tauri::Window<R>
|
||||
) -> std::result::Result<(), String> {
|
||||
set_pass_through_enabled(&window, false).map_err(|e| {
|
||||
log::error!("Failed to disable click-through: {e}");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
const PLUGIN_NAME: &str = "proj-airi-tauri-plugin-window-pass-through-on-hover";
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
PluginBuilder::new("proj-airi-tauri-plugin-window-pass-through-on-hover")
|
||||
let builder = tauri_specta::Builder::<R>::new()
|
||||
.plugin_name(PLUGIN_NAME)
|
||||
// ↓ HACKY WARNING
|
||||
// ↓ Below is a modified version from the expansion of `tauri_specta::collect_commands!`,
|
||||
// ↓ as the original macro does not accept a mix of commands with and without `#[specta::specta]`
|
||||
.commands(tauri_specta::internal::command(
|
||||
tauri::generate_handler![
|
||||
start_monitor,
|
||||
stop_monitor,
|
||||
start_pass_through,
|
||||
stop_pass_through
|
||||
],
|
||||
specta::function::collect_functions![
|
||||
start_pass_through::<tauri::Wry>,
|
||||
// ^^^^^^^^^^
|
||||
// TODO: We have to specify the runtime type here. This is a known issue:
|
||||
// - https://github.com/specta-rs/tauri-specta/issues/70
|
||||
// - https://github.com/specta-rs/tauri-specta/issues/162
|
||||
stop_pass_through::<tauri::Wry>,
|
||||
]
|
||||
));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
builder
|
||||
.export(
|
||||
Typescript::default(),
|
||||
"../src/commands/bindings/window-pass-through-on-hover.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
|
||||
PluginBuilder::new(PLUGIN_NAME)
|
||||
.setup(|app, _| {
|
||||
app.manage(WindowClickThroughState::default());
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
start_monitor,
|
||||
stop_monitor,
|
||||
start_pass_through,
|
||||
stop_pass_through,
|
||||
])
|
||||
.invoke_handler(builder.invoke_handler())
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#[cfg(debug_assertions)]
|
||||
use specta_typescript::Typescript;
|
||||
use tauri::{
|
||||
Manager,
|
||||
Runtime,
|
||||
@@ -7,27 +9,33 @@ use tauri::{
|
||||
use crate::app::windows::{chat, settings};
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn go<R: Runtime>(
|
||||
window: tauri::Window<R>,
|
||||
route: String,
|
||||
window_label: String,
|
||||
// ↓ #[specta(optional)] is not available in parameters.
|
||||
// ↓ This will be `string | null` in the generated TypeScript code
|
||||
window_label: Option<String>,
|
||||
) -> std::result::Result<(), String> {
|
||||
let app = window.app_handle();
|
||||
|
||||
let target_window = match window_label.as_str() {
|
||||
"chat" => match app.get_webview_window("chat") {
|
||||
let target_window = match window_label.as_deref() {
|
||||
Some("chat") => match app.get_webview_window("chat") {
|
||||
Some(window) => window,
|
||||
None => {
|
||||
chat::new_chat_window(app).map_err(|e| format!("Failed to create chat window: {}", e))?
|
||||
},
|
||||
},
|
||||
"settings" => match app.get_webview_window("settings") {
|
||||
Some("settings") => match app.get_webview_window("settings") {
|
||||
Some(window) => window,
|
||||
None => settings::new_settings_window(app)
|
||||
.map_err(|e| format!("Failed to create settings window: {}", e))?,
|
||||
},
|
||||
_ => {
|
||||
return Err(format!("Unknown window label: {}", window_label));
|
||||
Some(label) => {
|
||||
return Err(format!("Unknown window label: {}", label));
|
||||
},
|
||||
None => {
|
||||
return Err("Missing window label".into());
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,9 +51,23 @@ async fn go<R: Runtime>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const PLUGIN_NAME: &str = "proj-airi-tauri-plugin-window-router-link";
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("proj-airi-tauri-plugin-window-router-link")
|
||||
.invoke_handler(tauri::generate_handler![go])
|
||||
let builder = tauri_specta::Builder::<R>::new()
|
||||
.plugin_name(PLUGIN_NAME)
|
||||
.commands(tauri_specta::collect_commands![go::<tauri::Wry>]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
builder
|
||||
.export(
|
||||
Typescript::default(),
|
||||
"../src/commands/bindings/window-router-link.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
|
||||
Builder::new(PLUGIN_NAME)
|
||||
.invoke_handler(builder.invoke_handler())
|
||||
.setup(|_, _| Ok(()))
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
|
||||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||
|
||||
/** user-defined commands **/
|
||||
|
||||
|
||||
export const commands = {
|
||||
async startPassThrough() : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_pass_through") };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async stopPassThrough() : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_pass_through") };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** user-defined events **/
|
||||
|
||||
|
||||
|
||||
/** user-defined constants **/
|
||||
|
||||
|
||||
|
||||
/** user-defined types **/
|
||||
|
||||
|
||||
|
||||
/** tauri-specta globals **/
|
||||
|
||||
import {
|
||||
invoke as TAURI_INVOKE,
|
||||
Channel as TAURI_CHANNEL,
|
||||
} from "@tauri-apps/api/core";
|
||||
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
|
||||
import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
|
||||
|
||||
type __EventObj__<T> = {
|
||||
listen: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
||||
once: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
|
||||
emit: null extends T
|
||||
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
|
||||
};
|
||||
|
||||
export type Result<T, E> =
|
||||
| { status: "ok"; data: T }
|
||||
| { status: "error"; error: E };
|
||||
|
||||
function __makeEvents__<T extends Record<string, any>>(
|
||||
mappings: Record<keyof T, string>,
|
||||
) {
|
||||
return new Proxy(
|
||||
{} as unknown as {
|
||||
[K in keyof T]: __EventObj__<T[K]> & {
|
||||
(handle: __WebviewWindow__): __EventObj__<T[K]>;
|
||||
};
|
||||
},
|
||||
{
|
||||
get: (_, event) => {
|
||||
const name = mappings[event as keyof T];
|
||||
|
||||
return new Proxy((() => {}) as any, {
|
||||
apply: (_, __, [window]: [__WebviewWindow__]) => ({
|
||||
listen: (arg: any) => window.listen(name, arg),
|
||||
once: (arg: any) => window.once(name, arg),
|
||||
emit: (arg: any) => window.emit(name, arg),
|
||||
}),
|
||||
get: (_, command: keyof __EventObj__<any>) => {
|
||||
switch (command) {
|
||||
case "listen":
|
||||
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
|
||||
case "once":
|
||||
return (arg: any) => TAURI_API_EVENT.once(name, arg);
|
||||
case "emit":
|
||||
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||
|
||||
/** user-defined commands **/
|
||||
|
||||
|
||||
export const commands = {
|
||||
async go(route: string, windowLabel: string | null) : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:proj-airi-tauri-plugin-window-router-link|go", { route, windowLabel }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** user-defined events **/
|
||||
|
||||
|
||||
|
||||
/** user-defined constants **/
|
||||
|
||||
|
||||
|
||||
/** user-defined types **/
|
||||
|
||||
|
||||
|
||||
/** tauri-specta globals **/
|
||||
|
||||
import {
|
||||
invoke as TAURI_INVOKE,
|
||||
Channel as TAURI_CHANNEL,
|
||||
} from "@tauri-apps/api/core";
|
||||
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
|
||||
import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
|
||||
|
||||
type __EventObj__<T> = {
|
||||
listen: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
||||
once: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
|
||||
emit: null extends T
|
||||
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
|
||||
};
|
||||
|
||||
export type Result<T, E> =
|
||||
| { status: "ok"; data: T }
|
||||
| { status: "error"; error: E };
|
||||
|
||||
function __makeEvents__<T extends Record<string, any>>(
|
||||
mappings: Record<keyof T, string>,
|
||||
) {
|
||||
return new Proxy(
|
||||
{} as unknown as {
|
||||
[K in keyof T]: __EventObj__<T[K]> & {
|
||||
(handle: __WebviewWindow__): __EventObj__<T[K]>;
|
||||
};
|
||||
},
|
||||
{
|
||||
get: (_, event) => {
|
||||
const name = mappings[event as keyof T];
|
||||
|
||||
return new Proxy((() => {}) as any, {
|
||||
apply: (_, __, [window]: [__WebviewWindow__]) => ({
|
||||
listen: (arg: any) => window.listen(name, arg),
|
||||
once: (arg: any) => window.once(name, arg),
|
||||
emit: (arg: any) => window.emit(name, arg),
|
||||
}),
|
||||
get: (_, command: keyof __EventObj__<any>) => {
|
||||
switch (command) {
|
||||
case "listen":
|
||||
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
|
||||
case "once":
|
||||
return (arg: any) => TAURI_API_EVENT.once(name, arg);
|
||||
case "emit":
|
||||
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * as WindowPassThroughOnHover from './bindings/window-pass-through-on-hover'
|
||||
export * as WindowRouterLink from './bindings/window-router-link'
|
||||
@@ -1,18 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { useTauriCore } from '../../composables/tauri'
|
||||
import { WindowRouterLink } from '../../commands'
|
||||
|
||||
const props = defineProps<{
|
||||
to: string
|
||||
label?: string
|
||||
}>()
|
||||
|
||||
const { invoke } = useTauriCore()
|
||||
|
||||
function handleClick() {
|
||||
invoke('plugin:proj-airi-tauri-plugin-window-router-link|go', {
|
||||
route: props.to,
|
||||
windowLabel: props.label,
|
||||
})
|
||||
WindowRouterLink.commands.go(props.to, props.label ?? null)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -18,15 +18,8 @@ export interface InvokeMethods {
|
||||
// Plugin - Window Pass through on hover
|
||||
'plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_monitor': { args: undefined, options: undefined, returns: void }
|
||||
'plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_monitor': { args: undefined, options: undefined, returns: void }
|
||||
'plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_pass_through': { args: undefined, options: undefined, returns: void }
|
||||
'plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_pass_through': { args: undefined, options: undefined, returns: void }
|
||||
|
||||
// Plugin - WindowRouterLink
|
||||
'plugin:proj-airi-tauri-plugin-window-router-link|go': {
|
||||
args: { route: string, windowLabel?: string } | undefined
|
||||
options: undefined
|
||||
returns: void
|
||||
}
|
||||
}
|
||||
|
||||
export interface InvokeMethodShape {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useTauriCore } from '../composables/tauri'
|
||||
import { WindowPassThroughOnHover } from '../commands'
|
||||
|
||||
export async function startClickThrough() {
|
||||
const { invoke } = useTauriCore()
|
||||
await invoke('plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_pass_through')
|
||||
await WindowPassThroughOnHover.commands.startPassThrough()
|
||||
}
|
||||
|
||||
export async function stopClickThrough() {
|
||||
const { invoke } = useTauriCore()
|
||||
await invoke('plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_pass_through')
|
||||
await WindowPassThroughOnHover.commands.stopPassThrough()
|
||||
}
|
||||
|
||||
@@ -185,6 +185,7 @@ words:
|
||||
- sizecheck
|
||||
- smallserial
|
||||
- Sniglet
|
||||
- specta
|
||||
- ssml
|
||||
- staticlib
|
||||
- stepfun
|
||||
|
||||
@@ -8,6 +8,7 @@ export default await antfu(
|
||||
'**/assets/js/**',
|
||||
'**/assets/live2d/models/**',
|
||||
'apps/stage-tamagotchi/out/**',
|
||||
'apps/stage-tamagotchi/src/commands/bindings/**',
|
||||
'apps/stage-tamagotchi/src-tauri/**',
|
||||
'crates/**',
|
||||
'**/drizzle/**',
|
||||
|
||||
Reference in New Issue
Block a user