Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
5d2d407659 | |||
795e889bd0 | |||
7071f19877 | |||
a72ec25088 | |||
5893536daa | |||
e9550e8eee | |||
658a69a1e2 |
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,3 +1,18 @@
|
|||||||
|
## 0.10.1
|
||||||
|
|
||||||
|
- Fixed the order of cells in the CHUNITHM keyboard
|
||||||
|
- Fixed numpad bindings with numlock disabled
|
||||||
|
- Disabled primary monitor cleanup when "don't switch primary monitor" is enabled
|
||||||
|
|
||||||
|
## 0.10.0
|
||||||
|
|
||||||
|
- Added a global progress bar
|
||||||
|
- Fixed issues with downloading under certain conditions
|
||||||
|
|
||||||
|
## 0.9.0
|
||||||
|
|
||||||
|
- Added a light/dark theme switcher
|
||||||
|
|
||||||
## 0.8.1
|
## 0.8.1
|
||||||
|
|
||||||
- Hotfixed the program failing to launch if the data dir hadn't already been created
|
- Hotfixed the program failing to launch if the data dir hadn't already been created
|
||||||
|
1
TODO.md
1
TODO.md
@ -4,6 +4,5 @@
|
|||||||
|
|
||||||
### Long-term
|
### Long-term
|
||||||
|
|
||||||
- Progress bars and other GUI sugar
|
|
||||||
- artemis as a special package
|
- artemis as a special package
|
||||||
- Other arcade games (if there is demand)
|
- Other arcade games (if there is demand)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::{collections::HashSet, path::PathBuf};
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
use futures::Stream;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{AppHandle, Emitter};
|
use tauri::{AppHandle, Emitter};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
@ -15,7 +14,7 @@ pub struct DownloadHandler {
|
|||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct DownloadTick {
|
pub struct DownloadTick {
|
||||||
pkg_key: PkgKey,
|
pkg_key: PkgKey,
|
||||||
ratio: f32
|
ratio: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DownloadHandler {
|
impl DownloadHandler {
|
||||||
@ -50,14 +49,15 @@ impl DownloadHandler {
|
|||||||
|
|
||||||
let mut cache_file_w = File::create(&zip_path_part).await?;
|
let mut cache_file_w = File::create(&zip_path_part).await?;
|
||||||
let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream();
|
let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream();
|
||||||
let first_hint = byte_stream.size_hint().0 as f32;
|
let mut total_bytes = 0;
|
||||||
|
|
||||||
log::info!("downloading: {}", rmt.download_url);
|
log::info!("downloading: {}", rmt.download_url);
|
||||||
while let Some(item) = byte_stream.next().await {
|
while let Some(item) = byte_stream.next().await {
|
||||||
let i = item?;
|
let i = item?;
|
||||||
app.emit("download-tick", DownloadTick {
|
total_bytes += i.len();
|
||||||
|
_ = app.emit("download-progress", DownloadTick {
|
||||||
pkg_key: pkg_key.clone(),
|
pkg_key: pkg_key.clone(),
|
||||||
ratio: 1.0f32 - (byte_stream.size_hint().0 as f32) / first_hint
|
ratio: (total_bytes as f32) / (rmt.file_size as f32),
|
||||||
})?;
|
})?;
|
||||||
cache_file_w.write_all(&mut i.as_ref()).await?;
|
cache_file_w.write_all(&mut i.as_ref()).await?;
|
||||||
}
|
}
|
||||||
|
@ -102,14 +102,15 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.listen("download-end", closure!(clone apph, |ev| {
|
app.listen("download-end", closure!(clone apph, |ev| {
|
||||||
log::debug!("download-end triggered: {}", ev.payload());
|
|
||||||
let raw = ev.payload();
|
let raw = ev.payload();
|
||||||
|
log::debug!("download-end triggered: {}", raw);
|
||||||
let key = PkgKey(raw[1..raw.len()-1].to_owned());
|
let key = PkgKey(raw[1..raw.len()-1].to_owned());
|
||||||
let apph = apph.clone();
|
let apph = apph.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let mutex = apph.state::<Mutex<AppData>>();
|
let mutex = apph.state::<Mutex<AppData>>();
|
||||||
let mut appd = mutex.lock().await;
|
let mut appd = mutex.lock().await;
|
||||||
log::debug!("download-end install {:?}", appd.pkgs.install_package(&key, true, false).await);
|
let res = appd.pkgs.install_package(&key, true, false).await;
|
||||||
|
log::debug!("download-end install {:?}", res);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -126,19 +127,21 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
app.listen("install-end-prelude", closure!(clone apph, |ev| {
|
app.listen("install-end-prelude", closure!(clone apph, |ev| {
|
||||||
log::debug!("install-end-prelude triggered: {}", ev.payload());
|
|
||||||
let payload = serde_json::from_str::<Payload>(ev.payload());
|
let payload = serde_json::from_str::<Payload>(ev.payload());
|
||||||
|
log::debug!("install-end-prelude triggered: {:?}", payload);
|
||||||
let apph = apph.clone();
|
let apph = apph.clone();
|
||||||
if let Ok(payload) = payload {
|
if let Ok(payload) = payload {
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let mutex = apph.state::<Mutex<AppData>>();
|
let mutex = apph.state::<Mutex<AppData>>();
|
||||||
let mut appd = mutex.lock().await;
|
let mut appd = mutex.lock().await;
|
||||||
|
let res = appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf);
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"install-end-prelude toggle {:?}",
|
"install-end-prelude toggle {:?}",
|
||||||
appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf)
|
res
|
||||||
);
|
);
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
log::debug!("install-end {:?}", apph.emit("install-end", payload));
|
let res = apph.emit("install-end", payload);
|
||||||
|
log::debug!("install-end {:?}", res);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log::error!("install-end-prelude: invalid payload: {}", ev.payload());
|
log::error!("install-end-prelude: invalid payload: {}", ev.payload());
|
||||||
@ -232,7 +235,8 @@ pub async fn run(_args: Vec<String>) {
|
|||||||
let mutex = app.state::<Mutex<AppData>>();
|
let mutex = app.state::<Mutex<AppData>>();
|
||||||
let appd = mutex.lock().await;
|
let appd = mutex.lock().await;
|
||||||
if let Some(p) = &appd.profile {
|
if let Some(p) = &appd.profile {
|
||||||
log::debug!("save: {:?}", p.save());
|
let res = p.save();
|
||||||
|
log::debug!("save: {:?}", res);
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -22,4 +22,5 @@ pub struct V1Version {
|
|||||||
pub icon: String,
|
pub icon: String,
|
||||||
pub dependencies: BTreeSet<PkgKeyVersion>,
|
pub dependencies: BTreeSet<PkgKeyVersion>,
|
||||||
pub download_url: String,
|
pub download_url: String,
|
||||||
|
pub file_size: i64,
|
||||||
}
|
}
|
@ -6,14 +6,14 @@ use tauri::{AppHandle, Listener};
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DisplayInfo {
|
pub struct DisplayInfo {
|
||||||
pub primary: String,
|
pub primary: Option<String>,
|
||||||
pub set: Option<DisplaySet>,
|
pub set: Option<DisplaySet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DisplayInfo {
|
impl Default for DisplayInfo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
DisplayInfo {
|
DisplayInfo {
|
||||||
primary: "default".to_owned(),
|
primary: None,
|
||||||
set: query_displays().ok(),
|
set: query_displays().ok(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ impl Display {
|
|||||||
.ok_or_else(|| anyhow!("Unable to query display settings"))?;
|
.ok_or_else(|| anyhow!("Unable to query display settings"))?;
|
||||||
|
|
||||||
let res = DisplayInfo {
|
let res = DisplayInfo {
|
||||||
primary: primary.name().to_owned(),
|
primary: if self.dont_switch_primary { None } else { Some(primary.name().to_owned()) },
|
||||||
set: Some(display_set.clone()),
|
set: Some(display_set.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,12 +132,14 @@ impl Display {
|
|||||||
let display_set = info.set.as_ref()
|
let display_set = info.set.as_ref()
|
||||||
.ok_or_else(|| anyhow!("Unable to clean up displays: no display set"))?;
|
.ok_or_else(|| anyhow!("Unable to clean up displays: no display set"))?;
|
||||||
|
|
||||||
|
if let Some(info_primary) = &info.primary {
|
||||||
let primary = display_set
|
let primary = display_set
|
||||||
.displays()
|
.displays()
|
||||||
.find(|display| display.name() == info.primary)
|
.find(|display| display.name() == info_primary)
|
||||||
.ok_or_else(|| anyhow!("Display {} not found", info.primary))?;
|
.ok_or_else(|| anyhow!("Display {} not found", info_primary))?;
|
||||||
|
|
||||||
primary.set_primary()?;
|
primary.set_primary()?;
|
||||||
|
}
|
||||||
|
|
||||||
display_set.apply()?;
|
display_set.apply()?;
|
||||||
displayz::refresh()?;
|
displayz::refresh()?;
|
||||||
|
@ -81,6 +81,7 @@ pub struct Remote {
|
|||||||
pub nsfw: bool,
|
pub nsfw: bool,
|
||||||
pub categories: Vec<String>,
|
pub categories: Vec<String>,
|
||||||
pub dependencies: BTreeSet<PkgKey>,
|
pub dependencies: BTreeSet<PkgKey>,
|
||||||
|
pub file_size: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PkgKey {
|
impl PkgKey {
|
||||||
@ -112,7 +113,8 @@ impl Package {
|
|||||||
nsfw: p.has_nsfw_content,
|
nsfw: p.has_nsfw_content,
|
||||||
version: v.version_number,
|
version: v.version_number,
|
||||||
categories: p.categories,
|
categories: p.categories,
|
||||||
dependencies: Self::sanitize_deps(v.dependencies)
|
dependencies: Self::sanitize_deps(v.dependencies),
|
||||||
|
file_size: v.file_size
|
||||||
}),
|
}),
|
||||||
source: PackageSource::Rainy,
|
source: PackageSource::Rainy,
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,7 @@ pub struct PackageStore {
|
|||||||
offline: bool,
|
offline: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
pub pkg: PkgKey
|
pub pkg: PkgKey
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "STARTLINER",
|
"productName": "STARTLINER",
|
||||||
"version": "0.8.1",
|
"version": "0.10.1",
|
||||||
"identifier": "zip.patafour.startliner",
|
"identifier": "zip.patafour.startliner",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
|
@ -28,7 +28,9 @@ import {
|
|||||||
usePrfStore,
|
usePrfStore,
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import { Dirs } from '../types';
|
import { Dirs } from '../types';
|
||||||
import { messageSplit } from '../util';
|
import { messageSplit, shouldPreferDark } from '../util';
|
||||||
|
|
||||||
|
document.documentElement.classList.toggle('use-dark-mode', shouldPreferDark());
|
||||||
|
|
||||||
const pkg = usePkgStore();
|
const pkg = usePkgStore();
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
@ -86,6 +88,60 @@ listen<string>('launch-error', (event) => {
|
|||||||
errorMessage.value = event.payload;
|
errorMessage.value = event.payload;
|
||||||
errorHeader.value = 'Launch error';
|
errorHeader.value = 'Launch error';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface DownloadingStatus {
|
||||||
|
ratio: number;
|
||||||
|
pkg_key: string;
|
||||||
|
}
|
||||||
|
const downloading_status: Ref<DownloadingStatus[]> = ref([]);
|
||||||
|
|
||||||
|
const download_value = computed(() => {
|
||||||
|
return (
|
||||||
|
downloading_status.value.map((v) => v.ratio).reduce((a, v) => a * v) *
|
||||||
|
100
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadProgressText = computed(() => {
|
||||||
|
if (download_value.value < 7) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let pkgs = `${downloading_status.value.length} package${downloading_status.value.length === 1 ? '' : 's'}`;
|
||||||
|
if (download_value.value < 14) {
|
||||||
|
return pkgs;
|
||||||
|
} else {
|
||||||
|
return `${pkgs} (${Math.floor(download_value.value)}%)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
listen<DownloadingStatus>('download-progress', (event) => {
|
||||||
|
let status = downloading_status.value.find(
|
||||||
|
(v) => v.pkg_key === event.payload.pkg_key
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status === undefined) {
|
||||||
|
status = {
|
||||||
|
ratio: 0,
|
||||||
|
pkg_key: event.payload.pkg_key,
|
||||||
|
};
|
||||||
|
downloading_status.value.push(status);
|
||||||
|
}
|
||||||
|
status.ratio = event.payload.ratio;
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
if (status !== undefined) {
|
||||||
|
downloading_status.value = downloading_status.value.filter(
|
||||||
|
(v) => v.pkg_key !== event.payload.pkg_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.ratio === 1.0) {
|
||||||
|
remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => remove, 10_000);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -99,6 +155,16 @@ listen<string>('launch-error', (event) => {
|
|||||||
? 'main-scale-l'
|
? 'main-scale-l'
|
||||||
: 'main-scale-xl'
|
: 'main-scale-xl'
|
||||||
"
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="downloading_status.length > 0"
|
||||||
|
class="download-progress-bg"
|
||||||
|
></div>
|
||||||
|
<ProgressBar
|
||||||
|
v-if="downloading_status.length > 0"
|
||||||
|
:value="download_value"
|
||||||
|
class="download-progress"
|
||||||
|
>{{ downloadProgressText }}</ProgressBar
|
||||||
>
|
>
|
||||||
<ConfirmDialog>
|
<ConfirmDialog>
|
||||||
<template #message="{ message }">
|
<template #message="{ message }">
|
||||||
@ -351,4 +417,23 @@ body {
|
|||||||
.p-progressbar-label {
|
.p-progressbar-label {
|
||||||
transition-duration: 0s !important;
|
transition-duration: 0s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.download-progress {
|
||||||
|
position: fixed !important;
|
||||||
|
bottom: 0;
|
||||||
|
left: 5vw;
|
||||||
|
width: 90vw;
|
||||||
|
z-index: 10000 !important;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
.download-progress-bg {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 60px;
|
||||||
|
background-color: var(--p-surface-900);
|
||||||
|
border-top: 1px solid var(--p-surface-600);
|
||||||
|
z-index: 998;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { usePkgStore } from '../stores';
|
import { usePkgStore } from '../stores';
|
||||||
@ -11,20 +12,26 @@ const props = defineProps({
|
|||||||
pkg: Object as () => Package,
|
pkg: Object as () => Package,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const deleting = ref(false);
|
||||||
|
|
||||||
const remove = async () => {
|
const remove = async () => {
|
||||||
if (props.pkg === undefined) {
|
if (props.pkg === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleting.value = true;
|
||||||
|
|
||||||
await invoke('delete_package', {
|
await invoke('delete_package', {
|
||||||
key: pkgKey(props.pkg),
|
key: pkgKey(props.pkg),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
deleting.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Button
|
<Button
|
||||||
v-if="pkg?.loc && !pkg?.js.busy"
|
v-if="pkg?.loc && !pkg?.js.downloading"
|
||||||
rounded
|
rounded
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
severity="danger"
|
severity="danger"
|
||||||
@ -32,7 +39,7 @@ const remove = async () => {
|
|||||||
size="small"
|
size="small"
|
||||||
class="self-center ml-4"
|
class="self-center ml-4"
|
||||||
style="width: 2rem; height: 2rem"
|
style="width: 2rem; height: 2rem"
|
||||||
:loading="pkg?.js.busy"
|
:loading="deleting"
|
||||||
v-on:click="remove()"
|
v-on:click="remove()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -45,7 +52,7 @@ const remove = async () => {
|
|||||||
size="small"
|
size="small"
|
||||||
class="self-center ml-4"
|
class="self-center ml-4"
|
||||||
style="width: 2rem; height: 2rem"
|
style="width: 2rem; height: 2rem"
|
||||||
:loading="pkg?.js.busy"
|
:loading="pkg?.js.downloading"
|
||||||
v-on:click="async () => await pkgs.install(pkg)"
|
v-on:click="async () => await pkgs.install(pkg)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -15,9 +15,51 @@ const handleKey = (
|
|||||||
) => {
|
) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const keycode = toKeycode(event.code);
|
let keycode = toKeycode(event.code);
|
||||||
|
|
||||||
if (keycode !== null && button !== undefined) {
|
if (keycode !== null && button !== undefined) {
|
||||||
const data = prf.current!.data.keyboard!.data as any;
|
const data = prf.current!.data.keyboard!.data as any;
|
||||||
|
|
||||||
|
if (event.getModifierState('NumLock') === false) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'NumpadDecimal':
|
||||||
|
keycode = toKeycode('Delete');
|
||||||
|
break;
|
||||||
|
case 'Numpad0':
|
||||||
|
keycode = toKeycode('Insert');
|
||||||
|
break;
|
||||||
|
case 'Numpad1':
|
||||||
|
keycode = toKeycode('End');
|
||||||
|
break;
|
||||||
|
case 'Numpad2':
|
||||||
|
keycode = toKeycode('ArrowDown');
|
||||||
|
break;
|
||||||
|
case 'Numpad3':
|
||||||
|
keycode = toKeycode('PageDown');
|
||||||
|
break;
|
||||||
|
case 'Numpad4':
|
||||||
|
keycode = toKeycode('ArrowLeft');
|
||||||
|
break;
|
||||||
|
case 'Numpad5':
|
||||||
|
keycode = toKeycode('Clear');
|
||||||
|
break;
|
||||||
|
case 'Numpad6':
|
||||||
|
keycode = toKeycode('ArrowRight');
|
||||||
|
break;
|
||||||
|
case 'Numpad7':
|
||||||
|
keycode = toKeycode('Home');
|
||||||
|
break;
|
||||||
|
case 'Numpad8':
|
||||||
|
keycode = toKeycode('ArrowUp');
|
||||||
|
break;
|
||||||
|
case 'Numpad9':
|
||||||
|
keycode = toKeycode('PageUp');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (index !== undefined) {
|
if (index !== undefined) {
|
||||||
data[button][index] = keycode;
|
data[button][index] = keycode;
|
||||||
} else {
|
} else {
|
||||||
@ -75,7 +117,7 @@ const handleMouse = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getKey = (key: keyof OngekiButtons, index?: number) =>
|
const getKey = (key: keyof OngekiButtons, index?: number): any =>
|
||||||
computed(() => {
|
computed(() => {
|
||||||
const data = prf.current!.data.keyboard?.data as any;
|
const data = prf.current!.data.keyboard?.data as any;
|
||||||
const keycode =
|
const keycode =
|
||||||
@ -93,6 +135,7 @@ const KEY_MAP: { [key: number]: string } = {
|
|||||||
6: 'M5',
|
6: 'M5',
|
||||||
8: 'Backspace',
|
8: 'Backspace',
|
||||||
9: 'Tab',
|
9: 'Tab',
|
||||||
|
12: 'Clear',
|
||||||
13: 'Enter',
|
13: 'Enter',
|
||||||
19: 'Pause',
|
19: 'Pause',
|
||||||
20: 'CapsLock',
|
20: 'CapsLock',
|
||||||
@ -204,28 +247,45 @@ const toKeycode = (key: string): number | null => {
|
|||||||
return res ? parseInt(res) : null;
|
return res ? parseInt(res) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
small: Boolean,
|
small: Boolean,
|
||||||
verySmall: Boolean,
|
|
||||||
tall: Boolean,
|
tall: Boolean,
|
||||||
tooltip: String,
|
tooltip: String,
|
||||||
button: String,
|
button: String,
|
||||||
color: String,
|
color: String,
|
||||||
index: Number,
|
index: Number,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const modelValue = computed(() => {
|
||||||
|
return getKey(props.button as keyof OngekiButtons, props.index).value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fontSize = computed(() => {
|
||||||
|
if (!props.small) {
|
||||||
|
return '1rem';
|
||||||
|
}
|
||||||
|
const len = modelValue.value.length;
|
||||||
|
if (len < 5) {
|
||||||
|
return '1rem';
|
||||||
|
}
|
||||||
|
if (len < 7) {
|
||||||
|
return '0.75rem';
|
||||||
|
}
|
||||||
|
return '0.5rem';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<InputText
|
<InputText
|
||||||
:style="{
|
:style="{
|
||||||
width: small ? '3em' : '5em',
|
width: small ? '2.8rem' : '5rem',
|
||||||
height: small ? '3em' : tall ? '10em' : '5em',
|
height: small ? '2.8rem' : tall ? '10rem' : '5rem',
|
||||||
fontSize: small ? '0.9em' : '1em',
|
fontSize,
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
}"
|
}"
|
||||||
unstyled
|
unstyled
|
||||||
class="text-center buttoninputtext"
|
class="text-center buttoninputtext"
|
||||||
v-tooltip="tooltip"
|
v-tooltip="tooltip ? `${tooltip}: ${modelValue}` : undefined"
|
||||||
@contextmenu.prevent="() => {}"
|
@contextmenu.prevent="() => {}"
|
||||||
@keydown="(ev: KeyboardEvent) => handleKey(button, ev, index)"
|
@keydown="(ev: KeyboardEvent) => handleKey(button, ev, index)"
|
||||||
@mousedown="
|
@mousedown="
|
||||||
@ -233,7 +293,7 @@ defineProps({
|
|||||||
handleMouse(button as keyof OngekiButtons, ev, index)
|
handleMouse(button as keyof OngekiButtons, ev, index)
|
||||||
"
|
"
|
||||||
@focusout="() => (hasClickedM1Once = false)"
|
@focusout="() => (hasClickedM1Once = false)"
|
||||||
:model-value="getKey(button as keyof OngekiButtons, index) as any"
|
:model-value="modelValue"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -241,5 +301,7 @@ defineProps({
|
|||||||
.buttoninputtext {
|
.buttoninputtext {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid rgba(200, 200, 200, 0.3);
|
border: 1px solid rgba(200, 200, 200, 0.3);
|
||||||
|
overflow: scroll !important;
|
||||||
|
text-align: center !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -15,7 +15,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const pkgs = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
const groupCallIndex = ref(0);
|
|
||||||
const empty = ref(false);
|
const empty = ref(false);
|
||||||
const gameSublist: Ref<string[]> = ref([]);
|
const gameSublist: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
@ -46,10 +45,7 @@ const group = computed(() => {
|
|||||||
({ namespace }) => namespace
|
({ namespace }) => namespace
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (groupCallIndex.value > 0) {
|
|
||||||
empty.value = Object.keys(res).length === 0;
|
empty.value = Object.keys(res).length === 0;
|
||||||
}
|
|
||||||
groupCallIndex.value += 1;
|
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,17 +20,15 @@ const install = async () => {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (props.pkg !== undefined) {
|
if (props.pkg !== undefined) {
|
||||||
props.pkg.js.busy = false;
|
props.pkg.js.downloading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (rv === 'Deferred') { /* download progress */ }
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Button
|
<Button
|
||||||
v-if="needsUpdate(pkg) && !pkg?.js.busy"
|
v-if="needsUpdate(pkg) && !pkg?.js.downloading"
|
||||||
rounded
|
rounded
|
||||||
icon="pi pi-download"
|
icon="pi pi-download"
|
||||||
severity="success"
|
severity="success"
|
||||||
|
@ -124,7 +124,7 @@ const prf = usePrfStore();
|
|||||||
<div
|
<div
|
||||||
v-for="idx in Array(16)
|
v-for="idx in Array(16)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map((_, i) => 16 - i)"
|
.map((_, i) => 32 - 2 * i - 1)"
|
||||||
>
|
>
|
||||||
<KeyboardKey
|
<KeyboardKey
|
||||||
button="cell"
|
button="cell"
|
||||||
@ -142,7 +142,7 @@ const prf = usePrfStore();
|
|||||||
<div
|
<div
|
||||||
v-for="idx in Array(16)
|
v-for="idx in Array(16)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map((_, i) => 32 - i)"
|
.map((_, i) => 32 - 2 * i)"
|
||||||
>
|
>
|
||||||
<KeyboardKey
|
<KeyboardKey
|
||||||
button="cell"
|
button="cell"
|
||||||
|
@ -34,6 +34,15 @@ const verboseModel = computed({
|
|||||||
await client.setVerbose(value);
|
await client.setVerbose(value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const themeModel = computed({
|
||||||
|
get() {
|
||||||
|
return client.theme;
|
||||||
|
},
|
||||||
|
async set(value: 'light' | 'dark' | 'system') {
|
||||||
|
await client.setTheme(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -67,5 +76,18 @@ const verboseModel = computed({
|
|||||||
>
|
>
|
||||||
<ToggleSwitch v-model="verboseModel" />
|
<ToggleSwitch v-model="verboseModel" />
|
||||||
</OptionRow>
|
</OptionRow>
|
||||||
|
<OptionRow title="Theme">
|
||||||
|
<SelectButton
|
||||||
|
v-model="themeModel"
|
||||||
|
:options="[
|
||||||
|
{ title: 'System', value: 'system' },
|
||||||
|
{ title: 'Light', value: 'light' },
|
||||||
|
{ title: 'Dark', value: 'dark' },
|
||||||
|
]"
|
||||||
|
:allow-empty="false"
|
||||||
|
option-label="title"
|
||||||
|
option-value="value"
|
||||||
|
/>
|
||||||
|
</OptionRow>
|
||||||
</OptionCategory>
|
</OptionCategory>
|
||||||
</template>
|
</template>
|
||||||
|
@ -17,6 +17,9 @@ app.use(pinia);
|
|||||||
app.use(PrimeVue, {
|
app.use(PrimeVue, {
|
||||||
theme: {
|
theme: {
|
||||||
preset: Preset,
|
preset: Preset,
|
||||||
|
options: {
|
||||||
|
darkModeSelector: '.use-dark-mode',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
app.use(ConfirmationService);
|
app.use(ConfirmationService);
|
||||||
|
@ -6,7 +6,12 @@ import { PhysicalSize, getCurrentWindow } from '@tauri-apps/api/window';
|
|||||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
||||||
import { invoke, invoke_nopopup } from './invoke';
|
import { invoke, invoke_nopopup } from './invoke';
|
||||||
import { Dirs, Feature, Game, Package, Profile, ProfileMeta } from './types';
|
import { Dirs, Feature, Game, Package, Profile, ProfileMeta } from './types';
|
||||||
import { changePrimaryColor, hasFeature, pkgKey } from './util';
|
import {
|
||||||
|
changePrimaryColor,
|
||||||
|
hasFeature,
|
||||||
|
pkgKey,
|
||||||
|
shouldPreferDark,
|
||||||
|
} from './util';
|
||||||
|
|
||||||
type InstallStatus = {
|
type InstallStatus = {
|
||||||
pkg: string;
|
pkg: string;
|
||||||
@ -114,13 +119,13 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
listen<InstallStatus>('install-start', async (ev) => {
|
listen<InstallStatus>('install-start', async (ev) => {
|
||||||
const key = ev.payload.pkg;
|
const key = ev.payload.pkg;
|
||||||
await this.reload(key);
|
await this.reload(key);
|
||||||
this.pkg[key].js.busy = true;
|
this.pkg[key].js.downloading = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
listen<InstallStatus>('install-end', async (ev) => {
|
listen<InstallStatus>('install-end', async (ev) => {
|
||||||
const key = ev.payload.pkg;
|
const key = ev.payload.pkg;
|
||||||
await this.reload(key);
|
await this.reload(key);
|
||||||
this.pkg[key].js.busy = false;
|
this.pkg[key].js.downloading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -147,17 +152,22 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
|
|
||||||
async reloadWith(key: string, pkg: Package) {
|
async reloadWith(key: string, pkg: Package) {
|
||||||
if (this.pkg[key] === undefined) {
|
if (this.pkg[key] === undefined) {
|
||||||
this.pkg[key] = { js: { busy: false } } as Package;
|
this.pkg[key] = { js: { downloading: false } } as Package;
|
||||||
} else {
|
} else {
|
||||||
this.pkg[key].loc = null;
|
this.pkg[key].loc = null;
|
||||||
this.pkg[key].rmt = null;
|
this.pkg[key].rmt = null;
|
||||||
}
|
}
|
||||||
Object.assign(this.pkg[key], pkg);
|
Object.assign(this.pkg[key], pkg);
|
||||||
|
|
||||||
|
if (!pkg.js) {
|
||||||
|
pkg.js = { downloading: false };
|
||||||
|
}
|
||||||
|
|
||||||
if (pkg.rmt !== null) {
|
if (pkg.rmt !== null) {
|
||||||
pkg.rmt.categories.forEach((c) =>
|
pkg.rmt.categories.forEach((c) =>
|
||||||
this.availableCategories.add(c)
|
this.availableCategories.add(c)
|
||||||
);
|
);
|
||||||
|
pkg.js.downloading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -188,9 +198,8 @@ export const usePkgStore = defineStore('pkg', {
|
|||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
|
||||||
if (pkg !== undefined) {
|
if (pkg !== undefined) {
|
||||||
pkg.js.busy = false;
|
pkg.js.downloading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -356,6 +365,7 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
const offlineMode = ref(false);
|
const offlineMode = ref(false);
|
||||||
const enableAutoupdates = ref(true);
|
const enableAutoupdates = ref(true);
|
||||||
const verbose = ref(false);
|
const verbose = ref(false);
|
||||||
|
const theme: Ref<'light' | 'dark' | 'system'> = ref('system');
|
||||||
|
|
||||||
const scaleValue = (value: ScaleType) =>
|
const scaleValue = (value: ScaleType) =>
|
||||||
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
|
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
|
||||||
@ -406,6 +416,11 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
if (input.scaleFactor) {
|
if (input.scaleFactor) {
|
||||||
await setScaleFactor(input.scaleFactor);
|
await setScaleFactor(input.scaleFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.theme) {
|
||||||
|
theme.value = input.theme;
|
||||||
|
}
|
||||||
|
await setTheme(theme.value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Error reading client options: ${e}`);
|
console.error(`Error reading client options: ${e}`);
|
||||||
}
|
}
|
||||||
@ -436,6 +451,7 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
w: Math.floor(size.width),
|
w: Math.floor(size.width),
|
||||||
h: Math.floor(size.height),
|
h: Math.floor(size.height),
|
||||||
},
|
},
|
||||||
|
theme: theme.value,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -468,6 +484,21 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
await invoke('set_global_config', { field: 'verbose', value });
|
await invoke('set_global_config', { field: 'verbose', value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setTheme = async (value: 'light' | 'dark' | 'system') => {
|
||||||
|
if (value === 'dark') {
|
||||||
|
document.documentElement.classList.add('use-dark-mode');
|
||||||
|
} else if (value === 'light') {
|
||||||
|
document.documentElement.classList.remove('use-dark-mode');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.toggle(
|
||||||
|
'use-dark-mode',
|
||||||
|
shouldPreferDark()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
theme.value = value;
|
||||||
|
await save();
|
||||||
|
};
|
||||||
|
|
||||||
getCurrentWindow().onResized(async ({ payload }) => {
|
getCurrentWindow().onResized(async ({ payload }) => {
|
||||||
// For whatever reason this is 0 when minimized
|
// For whatever reason this is 0 when minimized
|
||||||
if (payload.width > 0) {
|
if (payload.width > 0) {
|
||||||
@ -480,6 +511,7 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
offlineMode,
|
offlineMode,
|
||||||
enableAutoupdates,
|
enableAutoupdates,
|
||||||
verbose,
|
verbose,
|
||||||
|
theme,
|
||||||
timeout,
|
timeout,
|
||||||
scaleModel,
|
scaleModel,
|
||||||
load,
|
load,
|
||||||
@ -488,5 +520,6 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
setOfflineMode,
|
setOfflineMode,
|
||||||
setAutoupdates,
|
setAutoupdates,
|
||||||
setVerbose,
|
setVerbose,
|
||||||
|
setTheme,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@ export interface Package {
|
|||||||
icon: string;
|
icon: string;
|
||||||
} | null;
|
} | null;
|
||||||
js: {
|
js: {
|
||||||
busy: boolean;
|
downloading: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,3 +59,7 @@ export const hasFeature = (pkg: Package | undefined, feature: Feature) => {
|
|||||||
export const messageSplit = (message: any) => {
|
export const messageSplit = (message: any) => {
|
||||||
return message.message?.split('\n');
|
return message.message?.split('\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const shouldPreferDark = () => {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user