[READ-ONLY] Mirror of https://github.com/probablykasper/remind-me-again. Toggleable cron reminders app for Mac, Linux and Windows
linux macos notifications reminder tauri windows
0

Configure Feed

Select the types of activity you want to include in your feed.

Use tauri-specta

Kasper (May 7, 2023, 3:38 AM +0200) 19b33d8c be96ab6f

+209 -60
+35
bindings.ts
··· 1 + // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. 2 + 3 + declare global { 4 + interface Window { 5 + __TAURI_INVOKE__<T>(cmd: string, args?: Record<string, unknown>): Promise<T>; 6 + } 7 + } 8 + 9 + const invoke = window.__TAURI_INVOKE__; 10 + 11 + export function errorPopup(msg: string) { 12 + return invoke<null>("error_popup", { msg }) 13 + } 14 + 15 + export function hide() { 16 + return invoke<null>("hide") 17 + } 18 + 19 + export function newGroup(group: Group) { 20 + return invoke<Group[]>("new_group", { group }) 21 + } 22 + 23 + export function getGroups() { 24 + return invoke<Group[]>("get_groups") 25 + } 26 + 27 + export function updateGroup(group: Group) { 28 + return invoke<Group[]>("update_group", { group }) 29 + } 30 + 31 + export function deleteGroup(index: number) { 32 + return invoke<Group[]>("delete_group", { index }) 33 + } 34 + 35 + export type Group = { title: string; description: string; enabled: boolean; id: string; cron: string }
+10
package-lock.json
··· 24 24 "svelte": "^3.59.0", 25 25 "svelte-check": "^3.3.1", 26 26 "tailwindcss": "^3.3.2", 27 + "tauri-specta": "^0.0.2", 27 28 "typescript": "^5.0.4", 28 29 "vite": "^4.3.5" 29 30 } ··· 3295 3296 }, 3296 3297 "engines": { 3297 3298 "node": ">=14.0.0" 3299 + } 3300 + }, 3301 + "node_modules/tauri-specta": { 3302 + "version": "0.0.2", 3303 + "resolved": "https://registry.npmjs.org/tauri-specta/-/tauri-specta-0.0.2.tgz", 3304 + "integrity": "sha512-ZgbJ/dnqHPn7Jsux7dMx7L8zMsG05Mt6rxR+ZWuSvvfWs5t+dPdHKc/4QEO3gXdQ5h1cJDwGUaNsEFoECbYkTA==", 3305 + "dev": true, 3306 + "peerDependencies": { 3307 + "@tauri-apps/api": "^1.1.0" 3298 3308 } 3299 3309 }, 3300 3310 "node_modules/text-table": {
+1
package.json
··· 30 30 "svelte": "^3.59.0", 31 31 "svelte-check": "^3.3.1", 32 32 "tailwindcss": "^3.3.2", 33 + "tauri-specta": "^0.0.2", 33 34 "typescript": "^5.0.4", 34 35 "vite": "^4.3.5" 35 36 },
+96
src-tauri/Cargo.lock
··· 3 3 version = 3 4 4 5 5 [[package]] 6 + name = "Inflector" 7 + version = "0.11.4" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 10 + 11 + [[package]] 6 12 name = "adler" 7 13 version = "1.0.2" 8 14 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 645 651 checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 646 652 647 653 [[package]] 654 + name = "document-features" 655 + version = "0.2.7" 656 + source = "registry+https://github.com/rust-lang/crates.io-index" 657 + checksum = "e493c573fce17f00dcab13b6ac057994f3ce17d1af4dc39bfd482b83c6eb6157" 658 + dependencies = [ 659 + "litrs", 660 + ] 661 + 662 + [[package]] 648 663 name = "dtoa" 649 664 version = "0.4.8" 650 665 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 664 679 version = "1.0.4" 665 680 source = "registry+https://github.com/rust-lang/crates.io-index" 666 681 checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" 682 + 683 + [[package]] 684 + name = "either" 685 + version = "1.8.1" 686 + source = "registry+https://github.com/rust-lang/crates.io-index" 687 + checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 667 688 668 689 [[package]] 669 690 name = "embed-resource" ··· 1360 1381 ] 1361 1382 1362 1383 [[package]] 1384 + name = "indoc" 1385 + version = "1.0.9" 1386 + source = "registry+https://github.com/rust-lang/crates.io-index" 1387 + checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" 1388 + 1389 + [[package]] 1390 + name = "indoc" 1391 + version = "2.0.1" 1392 + source = "registry+https://github.com/rust-lang/crates.io-index" 1393 + checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690" 1394 + 1395 + [[package]] 1363 1396 name = "infer" 1364 1397 version = "0.12.0" 1365 1398 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1386 1419 "hermit-abi 0.3.1", 1387 1420 "libc", 1388 1421 "windows-sys 0.48.0", 1422 + ] 1423 + 1424 + [[package]] 1425 + name = "itertools" 1426 + version = "0.10.5" 1427 + source = "registry+https://github.com/rust-lang/crates.io-index" 1428 + checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 1429 + dependencies = [ 1430 + "either", 1389 1431 ] 1390 1432 1391 1433 [[package]] ··· 1554 1596 version = "0.3.7" 1555 1597 source = "registry+https://github.com/rust-lang/crates.io-index" 1556 1598 checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" 1599 + 1600 + [[package]] 1601 + name = "litrs" 1602 + version = "0.2.3" 1603 + source = "registry+https://github.com/rust-lang/crates.io-index" 1604 + checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" 1557 1605 1558 1606 [[package]] 1559 1607 name = "lock_api" ··· 2401 2449 "rust-macios", 2402 2450 "serde", 2403 2451 "serde_json", 2452 + "specta", 2404 2453 "tauri", 2405 2454 "tauri-build", 2406 2455 "tauri-plugin-window-state", 2456 + "tauri-specta", 2407 2457 "tokio", 2408 2458 ] 2409 2459 ··· 2763 2813 ] 2764 2814 2765 2815 [[package]] 2816 + name = "specta" 2817 + version = "1.0.3" 2818 + source = "registry+https://github.com/rust-lang/crates.io-index" 2819 + checksum = "95aa33f3135e656964f468db8369ac8e0b77ac957fe8cf335960202d68966d3b" 2820 + dependencies = [ 2821 + "document-features", 2822 + "indoc 1.0.9", 2823 + "once_cell", 2824 + "paste", 2825 + "serde", 2826 + "serde_json", 2827 + "specta-macros", 2828 + "tauri", 2829 + "thiserror", 2830 + ] 2831 + 2832 + [[package]] 2833 + name = "specta-macros" 2834 + version = "1.0.3" 2835 + source = "registry+https://github.com/rust-lang/crates.io-index" 2836 + checksum = "f66517b551b511fa78fa94bc553c7baf4a8d81c68a90e7458ea74f3de70acf6c" 2837 + dependencies = [ 2838 + "Inflector", 2839 + "itertools", 2840 + "proc-macro2", 2841 + "quote", 2842 + "syn 1.0.109", 2843 + "termcolor", 2844 + ] 2845 + 2846 + [[package]] 2766 2847 name = "spin" 2767 2848 version = "0.9.8" 2768 2849 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3111 3192 "webview2-com", 3112 3193 "windows 0.39.0", 3113 3194 "wry", 3195 + ] 3196 + 3197 + [[package]] 3198 + name = "tauri-specta" 3199 + version = "1.0.0" 3200 + source = "registry+https://github.com/rust-lang/crates.io-index" 3201 + checksum = "23419889204476d5a70a04077d4628061a5bb667b18804a4572df2fe6ccb8ba5" 3202 + dependencies = [ 3203 + "heck 0.4.1", 3204 + "indoc 2.0.1", 3205 + "serde", 3206 + "serde_json", 3207 + "specta", 3208 + "tauri", 3209 + "thiserror", 3114 3210 ] 3115 3211 3116 3212 [[package]]
+2
src-tauri/Cargo.toml
··· 21 21 tauri-plugin-window-state = "0.1" 22 22 nanoid = "0.4" 23 23 atomicwrites = "0.4" 24 + tauri-specta = { version = "1.0", features = ["javascript", "typescript"] } 25 + specta = "1.0" 24 26 25 27 [target.'cfg(target_os = "macos")'.dependencies] 26 28 macos-app-nap = "0.0"
-8
src-tauri/src/data.rs
··· 1 1 use crate::notifications::Group; 2 2 use atomicwrites::{AtomicFile, OverwriteBehavior}; 3 3 use serde::{Deserialize, Serialize}; 4 - use serde_json::Value; 5 4 use std::env; 6 5 use std::io::Write; 7 6 use std::path::PathBuf; ··· 22 21 app_dir: app_dir.clone(), 23 22 reminders_file: app_dir.join("Settings.json"), 24 23 } 25 - } 26 - } 27 - 28 - pub fn to_json<T: Serialize>(data: &T) -> Result<Value, String> { 29 - match serde_json::to_value(data) { 30 - Ok(v) => Ok(v), 31 - Err(e) => throw!("Error serializing {}", e), 32 24 } 33 25 } 34 26
+19
src-tauri/src/main.rs
··· 22 22 } 23 23 24 24 #[command] 25 + #[specta::specta] 25 26 fn error_popup(msg: String, win: Window) { 26 27 eprintln!("Error: {}", msg); 27 28 thread::spawn(move || { ··· 77 78 app_paths, 78 79 &ctx.config().tauri.bundle.identifier, 79 80 ); 81 + 82 + #[cfg(debug_assertions)] 83 + { 84 + tauri_specta::ts::export( 85 + specta::collect_types![ 86 + error_popup, 87 + hide, 88 + notifications::new_group, 89 + notifications::get_groups, 90 + notifications::update_group, 91 + notifications::delete_group, 92 + ], 93 + "../bindings.ts", 94 + ) 95 + .unwrap(); 96 + println!("Generated TS types"); 97 + } 80 98 81 99 let app = tauri::Builder::default() 82 100 .invoke_handler(tauri::generate_handler![ ··· 219 237 window.set_focus().unwrap(); 220 238 } 221 239 #[command] 240 + #[specta::specta] 222 241 fn hide(app: AppHandle) { 223 242 let window = app.get_window("main").unwrap(); 224 243 window.unminimize().unwrap();
+19 -12
src-tauri/src/notifications.rs
··· 1 - use crate::data::{to_json, AppPaths, RemindersFile}; 1 + use crate::data::{AppPaths, RemindersFile}; 2 2 use async_cron_scheduler::cron; 3 3 use async_cron_scheduler::{Job, JobId, Scheduler}; 4 4 use chrono::offset::Local; 5 5 use nanoid::nanoid; 6 6 use serde::{Deserialize, Serialize}; 7 - use serde_json::Value; 7 + use specta::Type; 8 8 use std::str::FromStr; 9 9 use std::sync::Mutex; 10 10 use tauri::api::dialog; ··· 28 28 #[cfg(not(target_os = "macos"))] 29 29 static mut APP_IDENTIFIER: Option<String> = Option::None; 30 30 31 - #[derive(Serialize, Deserialize, Clone)] 31 + #[derive(Serialize, Deserialize, Clone, Type)] 32 32 pub struct Group { 33 33 pub title: String, 34 34 pub description: String, 35 35 pub enabled: bool, 36 36 pub id: String, 37 37 #[serde(skip)] 38 + #[specta(type = null)] 38 39 pub job_id: Option<JobId>, 39 40 pub cron: String, 40 41 } ··· 180 181 pub struct Data(pub Mutex<Instance>); 181 182 182 183 #[command] 183 - pub async fn new_group(mut group: Group, data: State<'_, Data>) -> Result<Value, String> { 184 + #[specta::specta] 185 + pub async fn new_group(group: Group, data: State<'_, Data>) -> Result<Vec<Group>, String> { 184 186 let mut data = data.0.lock().unwrap(); 187 + let mut group = group; 185 188 group.id = data.generate_id(); 186 189 data.add_group(group)?; 187 190 data.save()?; 188 - to_json(&data.file.groups) 191 + Ok(data.file.groups.clone()) 189 192 } 190 193 191 194 #[command] 192 - pub async fn get_groups(data: State<'_, Data>) -> Result<Value, String> { 195 + #[specta::specta] 196 + pub async fn get_groups(data: State<'_, Data>) -> Result<Vec<Group>, String> { 193 197 let data = data.0.lock().unwrap(); 194 - to_json(&data.file.groups) 198 + Ok(data.file.groups.clone()) 195 199 } 196 200 197 201 #[command] 198 - pub async fn update_group(mut group: Group, data: State<'_, Data>) -> Result<Value, String> { 202 + #[specta::specta] 203 + pub async fn update_group(group: Group, data: State<'_, Data>) -> Result<Vec<Group>, String> { 204 + let mut group = group; 199 205 let mut data = data.0.lock().unwrap(); 200 206 let i = match data.file.groups.iter().position(|g| g.id == group.id) { 201 207 Some(i) => i, ··· 207 213 } 208 214 data.file.groups[i] = group; 209 215 data.save()?; 210 - to_json(&data.file.groups) 216 + Ok(data.file.groups.clone()) 211 217 } 212 218 213 219 #[command] 214 - pub async fn delete_group(index: usize, data: State<'_, Data>) -> Result<Value, String> { 220 + #[specta::specta] 221 + pub async fn delete_group(index: u32, data: State<'_, Data>) -> Result<Vec<Group>, String> { 215 222 let mut data = data.0.lock().unwrap(); 216 - data.delete_group(index); 223 + data.delete_group(index.try_into().unwrap()); 217 224 data.save()?; 218 - to_json(&data.file.groups) 225 + Ok(data.file.groups.clone()) 219 226 }
+7 -7
src/App.svelte
··· 5 5 import './app.css' 6 6 import Item from './lib/Item.svelte' 7 7 import New from './lib/New.svelte' 8 - import { checkShortcut, runCmd } from './lib/helpers' 9 - import type { Group } from './lib/types' 8 + import { checkShortcut } from './lib/helpers' 9 + import type { Group } from '../../bindings' 10 10 import { onDestroy } from 'svelte' 11 11 import { event, os } from '@tauri-apps/api' 12 - import { invoke } from '@tauri-apps/api/tauri' 12 + import commands from './lib/commands' 13 13 14 14 let groupElements: Item[] = [] 15 15 let focusedGroup: number | null = null 16 16 17 17 let groups: Group[] = [] 18 - runCmd<Group[]>('get_groups').then((g) => { 18 + commands.getGroups().then((g) => { 19 19 groups = g 20 20 console.log(groups) 21 21 }) ··· 58 58 <svelte:body 59 59 on:keydown={async (e) => { 60 60 if (checkShortcut(e, 'Escape')) { 61 - invoke('hide') 61 + commands.hide() 62 62 e.preventDefault() 63 63 } 64 64 }} ··· 100 100 bind:this={groupElements[i]} 101 101 bind:group 102 102 onUpdate={async () => { 103 - groups = await runCmd('update_group', { group }) 103 + groups = await commands.updateGroup(group) 104 104 }} 105 105 onDelete={async () => { 106 - groups = await runCmd('delete_group', { index: i }) 106 + groups = await commands.deleteGroup(i) 107 107 }} 108 108 /> 109 109 </div>
-8
src/global.d.ts
··· 1 - declare type DndEvent = import('svelte-dnd-action').DndEvent 2 - declare namespace svelte.JSX { 3 - // eslint-disable-next-line @typescript-eslint/no-unused-vars 4 - interface HTMLAttributes<T> { 5 - onconsider?: (event: CustomEvent<DndEvent> & { target: EventTarget & T }) => void 6 - onfinalize?: (event: CustomEvent<DndEvent> & { target: EventTarget & T }) => void 7 - } 8 - }
+1 -1
src/lib/Edit.svelte
··· 1 1 <script lang="ts"> 2 2 import { slide, fly } from 'svelte/transition' 3 3 import { getCronText, invisibleCursorFix } from './helpers' 4 - import type { Group } from './types' 4 + import type { Group } from '../../bindings' 5 5 6 6 export let group: Group 7 7
+1 -1
src/lib/Item.svelte
··· 1 1 <script lang="ts"> 2 2 import { onMount, tick } from 'svelte' 3 - import type { Group } from './types' 3 + import type { Group } from '../../bindings' 4 4 import Switch from './Switch.svelte' 5 5 import { checkShortcut, getCronText, invisibleCursorFix } from './helpers' 6 6 import Edit from './Edit.svelte'
+4 -3
src/lib/New.svelte
··· 3 3 import { cubicOut } from 'svelte/easing' 4 4 import { slide } from 'svelte/transition' 5 5 import Edit from './Edit.svelte' 6 - import type { Group } from './types' 7 - import { checkShortcut, invisibleCursorFix, runCmd } from './helpers' 6 + import { checkShortcut, invisibleCursorFix } from './helpers' 7 + import commands from './commands' 8 + import type { Group } from '../../bindings' 8 9 9 10 export let onCreate: (newGroups: Group[]) => void 10 11 let editMode = false ··· 22 23 } 23 24 24 25 async function create(newGroup: Group) { 25 - const newGroups: Group[] = await runCmd('new_group', { group: newGroup }) 26 + const newGroups: Group[] = await commands.newGroup(newGroup) 26 27 editMode = false 27 28 onCreate(newGroups) 28 29 group = newBlank()
+14
src/lib/commands.ts
··· 1 + import * as c from '../../bindings' 2 + export default new Proxy({} as typeof c, { 3 + get: 4 + (_, property: string) => 5 + async (...args: unknown[]) => { 6 + try { 7 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 + return await (c as any)[property](...args) 9 + } catch (e) { 10 + c.errorPopup(String(e)) 11 + throw e 12 + } 13 + }, 14 + })
-13
src/lib/helpers.ts
··· 1 - import { invoke } from '@tauri-apps/api/tauri' 2 1 import crontrue from 'cronstrue' 3 - 4 - export function popup(msg: string) { 5 - invoke('error_popup', { msg }) 6 - } 7 - 8 - // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 - export async function runCmd<T = any>(cmd: string, options: { [key: string]: any } = {}) { 10 - return (await invoke(cmd, options).catch((msg) => { 11 - popup(msg) 12 - throw msg 13 - })) as T 14 - } 15 2 16 3 type ShortcutOptions = { 17 4 shift?: boolean
-7
src/lib/types.ts
··· 1 - export type Group = { 2 - title: string 3 - description: string 4 - enabled: boolean 5 - id: string 6 - cron: string 7 - }