2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-11-17 18:52:06 +00:00

chore: use PrimeVue components

This commit is contained in:
Jan Laupetin
2025-10-16 21:22:47 +01:00
parent 900337de8d
commit 78538d68f5
9 changed files with 241 additions and 148 deletions

View File

@@ -8,7 +8,7 @@ type MinimalOutputChunk = Pick<OutputChunk, "type" | "fileName" | "code">;
type MinimalOutputBundle = Record<string, MinimalOutputAsset | MinimalOutputChunk>; type MinimalOutputBundle = Record<string, MinimalOutputAsset | MinimalOutputChunk>;
function createVarName(fileName: string) { function createVarName(fileName: string) {
return fileName.replaceAll(".", "_").toUpperCase(); return fileName.replaceAll(/[\.-]/g, "_").toUpperCase();
} }
function transformAsset(asset: MinimalOutputAsset) { function transformAsset(asset: MinimalOutputAsset) {

View File

@@ -8,7 +8,10 @@
"name": "openassettools", "name": "openassettools",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@fontsource/inter": "^5.2.8",
"@primeuix/themes": "^1.2.5",
"pinia": "3.0.3", "pinia": "3.0.3",
"primevue": "^4.4.1",
"vue": "3.5.22" "vue": "3.5.22"
}, },
"devDependencies": { "devDependencies": {
@@ -1324,6 +1327,15 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@fontsource/inter": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz",
"integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1829,6 +1841,74 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@primeuix/styled": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.7.4.tgz",
"integrity": "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==",
"license": "MIT",
"dependencies": {
"@primeuix/utils": "^0.6.1"
},
"engines": {
"node": ">=12.11.0"
}
},
"node_modules/@primeuix/styles": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-1.2.5.tgz",
"integrity": "sha512-nypFRct/oaaBZqP4jinT0puW8ZIfs4u+l/vqUFmJEPU332fl5ePj6DoOpQgTLzo3OfmvSmz5a5/5b4OJJmmi7Q==",
"license": "MIT",
"dependencies": {
"@primeuix/styled": "^0.7.3"
}
},
"node_modules/@primeuix/themes": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@primeuix/themes/-/themes-1.2.5.tgz",
"integrity": "sha512-n3YkwJrHQaEESc/D/A/iD815sxp8cKnmzscA6a8Tm8YvMtYU32eCahwLLe6h5rywghVwxASWuG36XBgISYOIjQ==",
"license": "MIT",
"dependencies": {
"@primeuix/styled": "^0.7.3"
}
},
"node_modules/@primeuix/utils": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.6.1.tgz",
"integrity": "sha512-tQL/ZOPgCdD+NTimlUmhyD0ey8J1XmpZE4hDHM+/fnuBicVVmlKOd5HpS748LcOVRUKbWjmEPdHX4hi5XZoC1Q==",
"license": "MIT",
"engines": {
"node": ">=12.11.0"
}
},
"node_modules/@primevue/core": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@primevue/core/-/core-4.4.1.tgz",
"integrity": "sha512-RG56iDKIJT//EtntjQzOiWOHZZJczw/qWWtdL5vFvw8/QDS9DPKn8HLpXK7N5Le6KK1MLXUsxoiGTZK+poUFUg==",
"license": "MIT",
"dependencies": {
"@primeuix/styled": "^0.7.4",
"@primeuix/utils": "^0.6.1"
},
"engines": {
"node": ">=12.11.0"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/@primevue/icons": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@primevue/icons/-/icons-4.4.1.tgz",
"integrity": "sha512-UfDimrIjVdY6EziwieyV4zPKzW6mnKHKhy4Dgyjv2oI6pNeuim+onbJo1ce22PEGXW78vfblG/3/JIzVHFweqQ==",
"license": "MIT",
"dependencies": {
"@primeuix/utils": "^0.6.1",
"@primevue/core": "4.4.1"
},
"engines": {
"node": ">=12.11.0"
}
},
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.29", "version": "1.0.0-beta.29",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz",
@@ -5369,6 +5449,22 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/primevue": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/primevue/-/primevue-4.4.1.tgz",
"integrity": "sha512-JbHBa5k30pZ7mn/z4vYBOnyt5GrR15eM3X0wa3VanonxnFLYkTEx8OMh33aU6ndWeOfi7Ef57dOL3bTH+3f4hQ==",
"license": "MIT",
"dependencies": {
"@primeuix/styled": "^0.7.4",
"@primeuix/styles": "^1.2.5",
"@primeuix/utils": "^0.6.1",
"@primevue/core": "4.4.1",
"@primevue/icons": "4.4.1"
},
"engines": {
"node": ">=12.11.0"
}
},
"node_modules/proto-list": { "node_modules/proto-list": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",

View File

@@ -13,7 +13,10 @@
"format": "prettier --write **/*.{js,ts,vue,html,json,yml,yaml,md}" "format": "prettier --write **/*.{js,ts,vue,html,json,yml,yaml,md}"
}, },
"dependencies": { "dependencies": {
"@fontsource/inter": "^5.2.8",
"@primeuix/themes": "^1.2.5",
"pinia": "3.0.3", "pinia": "3.0.3",
"primevue": "^4.4.1",
"vue": "3.5.22" "vue": "3.5.22"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,16 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import Button from "primevue/button";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { webviewAddEventListener, webviewBinds } from "@/native"; import { webviewAddEventListener, webviewBinds } from "@/native";
import { useZoneStore } from "@/stores/ZoneStore"; import ProgressBar from "primevue/progressbar";
import SpinningLoader from "@/components/SpinningLoader.vue"; import ZoneSelector from "./components/ZoneSelector.vue";
const zoneStore = useZoneStore();
const loadingFastFile = ref(false); const loadingFastFile = ref(false);
const unlinkingFastFile = ref(false); const unlinkingFastFile = ref(false);
const lastPercentage = ref<number>(0); const lastPercentage = ref<number>(0);
const performingAction = computed<boolean>(() => loadingFastFile.value || unlinkingFastFile.value); const performingAction = computed<boolean>(() => loadingFastFile.value || unlinkingFastFile.value);
const progressBarWidth = computed<string>(() => `${lastPercentage.value * 100}%`);
async function openFastFileSelect() { async function openFastFileSelect() {
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] }); return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
@@ -32,7 +31,7 @@ async function onOpenFastFileClick() {
}) })
.finally(() => { .finally(() => {
loadingFastFile.value = false; loadingFastFile.value = false;
lastPercentage.value = 1; lastPercentage.value = 100;
}); });
} }
@@ -65,22 +64,16 @@ async function onUnlinkFastFileClick() {
} }
} finally { } finally {
unlinkingFastFile.value = false; unlinkingFastFile.value = false;
lastPercentage.value = 1; lastPercentage.value = 100;
} }
} }
function onUnloadClicked(zoneName: string) {
webviewBinds.unloadZone(zoneName).catch((e: string) => {
console.error("Failed to unload zone:", e);
});
}
webviewAddEventListener("zoneLoadProgress", (dto) => { webviewAddEventListener("zoneLoadProgress", (dto) => {
lastPercentage.value = dto.percentage; lastPercentage.value = Math.floor(dto.percentage * 1000) / 10;
}); });
webviewAddEventListener("zoneUnlinkProgress", (dto) => { webviewAddEventListener("zoneUnlinkProgress", (dto) => {
lastPercentage.value = dto.percentage; lastPercentage.value = Math.floor(dto.percentage * 1000) / 10;
}); });
</script> </script>
@@ -90,33 +83,28 @@ webviewAddEventListener("zoneUnlinkProgress", (dto) => {
<small>Nothing to see here yet, this is mainly for testing</small> <small>Nothing to see here yet, this is mainly for testing</small>
<div class="actions"> <div class="actions">
<button :disabled="performingAction" @click="onOpenFastFileClick"> <Button
<SpinningLoader v-if="loadingFastFile" class="loading" /> label="Load fastfile"
<span>Load fastfile</span> :disabled="performingAction"
</button> :loading="loadingFastFile"
<button :disabled="performingAction" @click="onUnlinkFastFileClick"> @click="onOpenFastFileClick"
<SpinningLoader v-if="unlinkingFastFile" class="loading" /> />
<span>Unlink fastfile</span> <Button
</button> label="Unlink fastfile"
:disabled="performingAction"
:loading="unlinkingFastFile"
@click="onUnlinkFastFileClick"
/>
</div> </div>
<div> <ZoneSelector />
<h3>Loaded zones:</h3>
<div class="zone-list">
<div v-for="zone in zoneStore.loadedZones" :key="zone" class="zone">
<span>{{ zone }}</span>
<button :disabled="performingAction" @click="onUnloadClicked(zone)">Unload</button>
</div>
</div>
</div>
<div class="progressbar-wrapper"> <ProgressBar
<div v-if="performingAction"
class="progressbar" class="progressbar"
:class="{ visible: performingAction }" :show-value="false"
:style="{ width: progressBarWidth }" :value="lastPercentage"
></div> />
</div>
</main> </main>
</template> </template>
@@ -127,10 +115,6 @@ webviewAddEventListener("zoneUnlinkProgress", (dto) => {
column-gap: 0.5em; column-gap: 0.5em;
} }
.loading {
margin-right: 0.2em;
}
.zone-list { .zone-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -141,23 +125,21 @@ webviewAddEventListener("zoneUnlinkProgress", (dto) => {
margin-left: 0.5em; margin-left: 0.5em;
} }
.progressbar-wrapper { @starting-style {
.progressbar {
opacity: 0;
}
}
.progressbar {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
padding: 0.35rem 0.4rem;
}
.progressbar {
opacity: 0;
height: 0.4rem; height: 0.4rem;
border-radius: 2.5rem;
background-color: #b9772c;
transition: opacity 0.2s ease-in-out;
&.visible { transition: opacity 0.2s ease-in-out;
opacity: 1; opacity: 1;
}
} }
</style> </style>

View File

@@ -0,0 +1,35 @@
import type { App } from "vue";
import PrimeVue from "primevue/config";
import Aura from "@primeuix/themes/aura";
import { definePreset } from "@primeuix/themes";
const ModManTheme = definePreset(Aura, {
semantic: {
primary: {
50: "{orange.50}",
100: "{orange.100}",
200: "{orange.200}",
300: "{orange.300}",
400: "{orange.400}",
500: "{orange.500}",
600: "{orange.600}",
700: "{orange.700}",
800: "{orange.800}",
900: "{orange.900}",
950: "{orange.950}",
},
},
});
export function configurePrimeVue(app: App) {
app.use(PrimeVue, {
theme: {
preset: ModManTheme,
options: {
darkModeSelector: ".dark-mode",
},
},
});
// Always make dark mode for now
document.documentElement.classList.add("dark-mode");
}

View File

@@ -0,0 +1,54 @@
<script setup lang="ts">
import Button from "primevue/button";
import Listbox from "primevue/listbox";
import { ref, watch } from "vue";
import { useZoneStore } from "@/stores/ZoneStore";
import { webviewBinds } from "@/native";
const zoneStore = useZoneStore();
const selectedZone = ref<string | null>(null);
function onUnloadClicked() {
if (!selectedZone.value) return;
webviewBinds.unloadZone(selectedZone.value).catch((e: string) => {
console.error("Failed to unload zone:", e);
});
}
watch(
() => zoneStore.loadedZones,
(newValue) => {
// Reset selection if unloaded
if (!selectedZone.value) return;
if (newValue.indexOf(selectedZone.value) >= 0) return;
selectedZone.value = null;
},
{ deep: true },
);
</script>
<template>
<div class="zone-selector">
<div class="zone-list">
<Listbox v-model="selectedZone" :options="zoneStore.loadedZones" class="zone" />
</div>
<div class="zone-actions">
<Button label="Unload" :disabled="!selectedZone" @click="onUnloadClicked" />
</div>
</div>
</template>
<style lang="scss" scoped>
.zone-selector {
display: flex;
flex-direction: row;
width: 100%;
& > * {
width: 50%;
padding: 1rem 2rem;
}
}
</style>

View File

@@ -1,11 +1,20 @@
:root { @import "@fontsource/inter/latin-300.css";
@import "@fontsource/inter/latin-300-italic.css";
@import "@fontsource/inter/latin-400.css";
@import "@fontsource/inter/latin-400-italic.css";
@import "@fontsource/inter/latin-600.css";
@import "@fontsource/inter/latin-600-italic.css";
@import "@fontsource/inter/latin-700.css";
@import "@fontsource/inter/latin-700-italic.css";
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif; font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
font-weight: 400; font-weight: 400;
color: #0f0f0f; color: var(--p-text-color);
background-color: #f6f6f6; background-color: var(--p-content-background);
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
@@ -33,92 +42,3 @@ body {
height: 100vh; height: 100vh;
} }
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
display: flex;
justify-content: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
text-align: center;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
}
button:not(:disabled):hover {
border-color: #396cd8;
}
button:not(:disabled):active {
border-color: #396cd8;
background-color: #e8e8e8;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
input,
button {
outline: none;
}
#greet-input {
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:not(:disabled):active {
background-color: #0f0f0f69;
}
}

View File

@@ -3,6 +3,7 @@ import "./main.scss";
import { createApp } from "vue"; import { createApp } from "vue";
import { createPinia } from "pinia"; import { createPinia } from "pinia";
import { configurePrimeVue } from "./PrimeVue.ts";
import App from "./App.vue"; import App from "./App.vue";
@@ -10,4 +11,6 @@ const app = createApp(App);
app.use(createPinia()); app.use(createPinia());
configurePrimeVue(app);
app.mount("#app"); app.mount("#app");

View File

@@ -1,4 +1,4 @@
import { readonly, ref } from "vue"; import { ref } from "vue";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { webviewAddEventListener } from "@/native"; import { webviewAddEventListener } from "@/native";
@@ -16,5 +16,5 @@ export const useZoneStore = defineStore("zone", () => {
} }
}); });
return { loadedZones: readonly(loadedZones) }; return { loadedZones };
}); });