Fully implement settings
This commit is contained in:
8
app.go
8
app.go
@@ -55,6 +55,10 @@ func (a *App) GetLastPer100(name string) WailsPer100 {
|
||||
func (a *App) GetSettings() settings {
|
||||
return Settings
|
||||
}
|
||||
func (a *App) SetSetting(key string, value any) settings {
|
||||
return settingsService.Set(key, value)
|
||||
func (a *App) SetSetting(key string, value int64) WailsGenericAck {
|
||||
_, err := settingsService.Set(key, value)
|
||||
if err != nil {
|
||||
return WailsGenericAck{Success: false, Error: err.Error()}
|
||||
}
|
||||
return WailsGenericAck{Success: true}
|
||||
}
|
||||
|
@@ -21,7 +21,11 @@
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.0.7"
|
||||
"vite": "^3.0.7",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.20",
|
||||
|
@@ -1 +1 @@
|
||||
0539f67989f30cc4f97afd5c6035bcad
|
||||
bd9b801d4541f25052f0d4cb72b7d95a
|
50
frontend/pnpm-lock.yaml
generated
50
frontend/pnpm-lock.yaml
generated
@@ -18,6 +18,18 @@ importers:
|
||||
specifier: ^3.1.5
|
||||
version: 3.1.5(chart.js@4.4.3)(svelte@3.59.2)
|
||||
devDependencies:
|
||||
'@fortawesome/fontawesome-svg-core':
|
||||
specifier: ^6.5.2
|
||||
version: 6.6.0
|
||||
'@fortawesome/free-brands-svg-icons':
|
||||
specifier: ^6.5.2
|
||||
version: 6.6.0
|
||||
'@fortawesome/free-regular-svg-icons':
|
||||
specifier: ^6.5.2
|
||||
version: 6.6.0
|
||||
'@fortawesome/free-solid-svg-icons':
|
||||
specifier: ^6.5.2
|
||||
version: 6.6.0
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^1.0.1
|
||||
version: 1.4.0(svelte@3.59.2)(vite@3.2.10(sass@1.77.8))
|
||||
@@ -73,6 +85,26 @@ packages:
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@fortawesome/fontawesome-common-types@6.6.0':
|
||||
resolution: {integrity: sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/fontawesome-svg-core@6.6.0':
|
||||
resolution: {integrity: sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-brands-svg-icons@6.6.0':
|
||||
resolution: {integrity: sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-regular-svg-icons@6.6.0':
|
||||
resolution: {integrity: sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@fortawesome/free-solid-svg-icons@6.6.0':
|
||||
resolution: {integrity: sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -947,6 +979,24 @@ snapshots:
|
||||
'@esbuild/linux-loong64@0.15.18':
|
||||
optional: true
|
||||
|
||||
'@fortawesome/fontawesome-common-types@6.6.0': {}
|
||||
|
||||
'@fortawesome/fontawesome-svg-core@6.6.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.6.0
|
||||
|
||||
'@fortawesome/free-brands-svg-icons@6.6.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.6.0
|
||||
|
||||
'@fortawesome/free-regular-svg-icons@6.6.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.6.0
|
||||
|
||||
'@fortawesome/free-solid-svg-icons@6.6.0':
|
||||
dependencies:
|
||||
'@fortawesome/fontawesome-common-types': 6.6.0
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
|
@@ -70,6 +70,7 @@
|
||||
scope="row"
|
||||
>
|
||||
</th>
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<td
|
||||
bind:innerText={name}
|
||||
class:border-[3px]={!name}
|
||||
|
@@ -58,7 +58,7 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative flex flex-col flex-grow h-[95vh]" data-vaul-drawer-wrapper id="page">
|
||||
<div class="relative flex flex-col flex-grow h-[93vh] select-none" data-vaul-drawer-wrapper id="page">
|
||||
<div class="relative overflow-auto h-full shadow-md sm:rounded-lg">
|
||||
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs text-gray-700 uppercase dark:text-gray-400">
|
||||
|
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { link, location } from "svelte-spa-router";
|
||||
import { faGear } from "@fortawesome/free-solid-svg-icons";
|
||||
import Fa from "svelte-fa";
|
||||
import Settings from "./Settings/Settings.svelte";
|
||||
Fa;
|
||||
|
||||
type Link = {
|
||||
label: string;
|
||||
@@ -38,6 +42,8 @@
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let showModal = true;
|
||||
</script>
|
||||
|
||||
<header
|
||||
@@ -75,4 +81,12 @@
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="absolute right-0 pt-4 pb-4 pr-8 pl-8 cursor-pointer" on:click={() => (showModal = true)}>
|
||||
<button>
|
||||
<Fa icon={faGear} scale={2} />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Settings bind:showModal />
|
||||
|
50
frontend/src/lib/components/Modal.svelte
Normal file
50
frontend/src/lib/components/Modal.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
export let showModal: boolean;
|
||||
|
||||
let dialog: HTMLDialogElement;
|
||||
|
||||
$: if (dialog && showModal) dialog.showModal();
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<dialog
|
||||
bind:this={dialog}
|
||||
class="min-w-[40vw] rounded-lg border-0 p-0 bg-gray-800 text-white"
|
||||
on:close={() => (showModal = false)}
|
||||
on:click|self={() => dialog.close()}
|
||||
>
|
||||
<div class="p-6" on:click|stopPropagation>
|
||||
<slot name="header" />
|
||||
<hr class="my-2 border-gray-600" />
|
||||
<slot />
|
||||
<hr class="my-2 border-gray-600" />
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
dialog::backdrop {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
dialog[open] {
|
||||
animation: zoom 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
@keyframes zoom {
|
||||
from {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
dialog[open]::backdrop {
|
||||
animation: fade 0.2s ease-out;
|
||||
}
|
||||
@keyframes fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
43
frontend/src/lib/components/Settings/Setting.svelte
Normal file
43
frontend/src/lib/components/Settings/Setting.svelte
Normal file
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { settingsStore } from "$lib/store/SettingsStore";
|
||||
import { SetSetting } from "$wails/main/App";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
export let key: string;
|
||||
export let setting: number;
|
||||
|
||||
let editSetting = String(setting);
|
||||
async function updateSetting() {
|
||||
const numSetting = parseInt(editSetting);
|
||||
if (isNaN(numSetting)) {
|
||||
console.log("Invalid setting, must be number");
|
||||
editSetting = String(setting);
|
||||
return;
|
||||
}
|
||||
const res = await SetSetting(key, numSetting);
|
||||
if (!res.success) {
|
||||
toast.error(`Failed to set setting with error ${res.error}`);
|
||||
editSetting = String(setting);
|
||||
return
|
||||
}
|
||||
setting = numSetting;
|
||||
settingsStore.update((store) => {
|
||||
// @ts-ignore
|
||||
store[key] = numSetting;
|
||||
return store;
|
||||
});
|
||||
toast.success(`Successfully set setting ${key} to ${numSetting}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<span class="w-[30vw] capitalize">{key}</span>
|
||||
<span
|
||||
contenteditable="true"
|
||||
on:focusout={updateSetting}
|
||||
class="w-[10vw] ml-2 p-1 border rounded bg-slate-900"
|
||||
bind:innerText={editSetting}
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
17
frontend/src/lib/components/Settings/Settings.svelte
Normal file
17
frontend/src/lib/components/Settings/Settings.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { settingsStore } from "$lib/store/SettingsStore";
|
||||
import Modal from "../Modal.svelte";
|
||||
import Setting from "./Setting.svelte";
|
||||
|
||||
export let showModal: boolean = false;
|
||||
</script>
|
||||
|
||||
<Modal bind:showModal>
|
||||
<h2 slot="header" class="select-none text-2xl font-bold">Settings</h2>
|
||||
|
||||
<div class="flex flex-2 flex-col gap-4 text-left text-xl">
|
||||
{#each Object.keys($settingsStore) as key}
|
||||
<Setting key={key} setting={$settingsStore[key]} />
|
||||
{/each}
|
||||
</div>
|
||||
</Modal>
|
@@ -2,6 +2,7 @@ import { type Writable, writable } from "svelte/store";
|
||||
import { main } from "$wails/models";
|
||||
import { GetFood } from "$wails/main/App";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { settingsStore } from "./SettingsStore";
|
||||
|
||||
async function createStore(): Promise<Writable<main.Food[]>> {
|
||||
let foods: main.Food[] = [];
|
||||
@@ -29,4 +30,10 @@ async function createStore(): Promise<Writable<main.Food[]>> {
|
||||
};
|
||||
}
|
||||
|
||||
export const foodStore = await createStore();
|
||||
const foodStore = await createStore();
|
||||
settingsStore.subscribe((settings) => {
|
||||
// @ts-ignore
|
||||
foodStore.refresh();
|
||||
});
|
||||
|
||||
export {foodStore}
|
||||
|
2
frontend/wailsjs/go/main/App.d.ts
vendored
2
frontend/wailsjs/go/main/App.d.ts
vendored
@@ -10,6 +10,6 @@ export function GetLastPer100(arg1:string):Promise<main.WailsPer100>;
|
||||
|
||||
export function GetSettings():Promise<main.settings>;
|
||||
|
||||
export function SetSetting(arg1:string,arg2:any):Promise<main.settings>;
|
||||
export function SetSetting(arg1:string,arg2:number):Promise<main.WailsGenericAck>;
|
||||
|
||||
export function UpdateFood(arg1:main.Food):Promise<main.WailsFood1>;
|
||||
|
@@ -92,6 +92,20 @@ export namespace main {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class WailsGenericAck {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new WailsGenericAck(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.success = source["success"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
}
|
||||
export class WailsPer100 {
|
||||
data: number;
|
||||
success: boolean;
|
||||
|
2
main.go
2
main.go
@@ -51,7 +51,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
settingsService := SettingsService{db: &db}
|
||||
settingsService = &SettingsService{db: &db}
|
||||
err = settingsService.LoadSettings()
|
||||
if err != nil {
|
||||
Error.Printf("%++v", err)
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -60,24 +61,27 @@ func (s *SettingsService) LoadSettings() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *SettingsService) Set(key string, value any) settings {
|
||||
func (s *SettingsService) Set(key string, value int64) (settings, error) {
|
||||
if s == nil || s.db == nil || !s.db.Ready {
|
||||
return Settings, fmt.Errorf("cannot set setting, db is nil or is not ready")
|
||||
}
|
||||
|
||||
key = strings.ToLower(key)
|
||||
fields := reflect.ValueOf(&Settings).Elem()
|
||||
for i := 0; i < fields.NumField(); i++ {
|
||||
field := fields.Type().Field(i)
|
||||
if field.Name == key {
|
||||
fields.Field(i).SetInt(value.(int64))
|
||||
fieldName := strings.ToLower(field.Name)
|
||||
if fieldName == key {
|
||||
fields.Field(i).SetInt(value)
|
||||
|
||||
// This could cause desync between client and server
|
||||
// But we can just refresh, I think it's fine
|
||||
go func() {
|
||||
_, err := s.db.writeConn.Exec("UPDATE settings SET value = ? WHERE key = ?", value, key)
|
||||
if err != nil {
|
||||
Error.Printf("error updating setting for key %s: %v", key)
|
||||
}
|
||||
}()
|
||||
_, err := s.db.writeConn.Exec("UPDATE settings SET value = ? WHERE key = ?", value, key)
|
||||
if err != nil {
|
||||
Error.Printf("error updating setting for key %s: %v", key, err)
|
||||
return Settings, err
|
||||
}
|
||||
|
||||
return Settings
|
||||
return Settings, nil
|
||||
}
|
||||
}
|
||||
return Settings
|
||||
return Settings, fmt.Errorf("key %s not found", key)
|
||||
}
|
@@ -31,6 +31,11 @@ type (
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
WailsGenericAck struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
Weight struct {
|
||||
rowid int
|
||||
date time.Time
|
||||
|
Reference in New Issue
Block a user