very hot looking puppet preview and move skin btn to scene view
This commit is contained in:
@@ -12,8 +12,6 @@
|
||||
let dirty = $state(false);
|
||||
let busy = $state(false);
|
||||
let error = $state("");
|
||||
let skinFile = $state<File | null>(null);
|
||||
let skinInput = $state<HTMLInputElement | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (!dirty) displayName = $profile?.displayName ?? "";
|
||||
@@ -23,15 +21,12 @@
|
||||
onMount(() => register("general", { apply, reset }, { dirty, busy }));
|
||||
|
||||
function updateDirty() {
|
||||
dirty =
|
||||
displayName.trim() !== ($profile?.displayName ?? "") || skinFile !== null;
|
||||
dirty = displayName.trim() !== ($profile?.displayName ?? "");
|
||||
error = "";
|
||||
}
|
||||
|
||||
function reset() {
|
||||
displayName = $profile?.displayName ?? "";
|
||||
skinFile = null;
|
||||
if (skinInput) skinInput.value = "";
|
||||
dirty = false;
|
||||
error = "";
|
||||
}
|
||||
@@ -47,13 +42,8 @@
|
||||
busy = true;
|
||||
error = "";
|
||||
try {
|
||||
const skinData = skinFile
|
||||
? Array.from(new Uint8Array(await skinFile.arrayBuffer()))
|
||||
: null;
|
||||
await commands.updateProfile(nextName, skinData);
|
||||
await commands.updateProfile(nextName, null);
|
||||
displayName = nextName;
|
||||
skinFile = null;
|
||||
if (skinInput) skinInput.value = "";
|
||||
dirty = false;
|
||||
return true;
|
||||
} catch (cause) {
|
||||
@@ -88,34 +78,6 @@
|
||||
This name can be changed at any time.
|
||||
</p>
|
||||
|
||||
<label class="fieldset-label mt-2" for="skin-file">Character skin</label>
|
||||
<div class="join w-full">
|
||||
<input
|
||||
class="input join-item min-w-0 flex-1"
|
||||
value={skinFile?.name ?? ($profile?.skinHash ? "Custom skin" : "Default skin")}
|
||||
readonly
|
||||
aria-label="Selected character skin"
|
||||
/>
|
||||
<button
|
||||
class="btn join-item"
|
||||
type="button"
|
||||
disabled={!$profile || busy}
|
||||
onclick={() => skinInput?.click()}>Choose PNG</button
|
||||
>
|
||||
</div>
|
||||
<input
|
||||
id="skin-file"
|
||||
class="hidden"
|
||||
type="file"
|
||||
accept="image/png"
|
||||
bind:this={skinInput}
|
||||
onchange={(event) => {
|
||||
skinFile = event.currentTarget.files?.[0] ?? null;
|
||||
updateDirty();
|
||||
}}
|
||||
/>
|
||||
<p class="fieldset-label">64×64 Minecraft-format PNG, base layer only.</p>
|
||||
|
||||
<label class="fieldset-label mt-2" for="public-key"
|
||||
>Identification Key
|
||||
<div
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
sceneConfiguration,
|
||||
sceneConfigurationListenerError,
|
||||
} from "$lib/listeners/scene-configuration";
|
||||
import { onMount } from "svelte";
|
||||
import { profile, profileListenerError } from "$lib/listeners/profile";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import PuppetPreview from "../../../routes/scene/components/renderer/puppet/preview.svelte";
|
||||
import PanelMessage from "./panel-message.svelte";
|
||||
import type { RegisterPanel } from "./types";
|
||||
|
||||
@@ -13,6 +15,9 @@
|
||||
let puppetScale = $state(1);
|
||||
let puppetOpacity = $state(1);
|
||||
let puppetMovementMode = $state<PuppetMovementMode>("free");
|
||||
let skinFile = $state<File | null>(null);
|
||||
let skinPreviewUrl = $state<string | null>(null);
|
||||
let skinInput = $state<HTMLInputElement | null>(null);
|
||||
let dirty = $state(false);
|
||||
let busy = $state(false);
|
||||
let error = $state("");
|
||||
@@ -27,19 +32,42 @@
|
||||
});
|
||||
|
||||
onMount(() => register("scene", { apply, reset }, { dirty, busy }));
|
||||
onDestroy(() => {
|
||||
if (skinPreviewUrl) URL.revokeObjectURL(skinPreviewUrl);
|
||||
});
|
||||
|
||||
function updateDirty() {
|
||||
dirty =
|
||||
function hasConfigurationChanges() {
|
||||
return (
|
||||
puppetScale !== $sceneConfiguration.puppetScale ||
|
||||
puppetOpacity !== $sceneConfiguration.puppetOpacity ||
|
||||
puppetMovementMode !== $sceneConfiguration.puppetMovementMode;
|
||||
puppetMovementMode !== $sceneConfiguration.puppetMovementMode
|
||||
);
|
||||
}
|
||||
|
||||
function updateDirty() {
|
||||
dirty = hasConfigurationChanges() || skinFile !== null;
|
||||
error = "";
|
||||
}
|
||||
|
||||
function clearSkinDraft() {
|
||||
if (skinPreviewUrl) URL.revokeObjectURL(skinPreviewUrl);
|
||||
skinFile = null;
|
||||
skinPreviewUrl = null;
|
||||
if (skinInput) skinInput.value = "";
|
||||
}
|
||||
|
||||
function selectSkinFile(file: File | null) {
|
||||
clearSkinDraft();
|
||||
skinFile = file;
|
||||
skinPreviewUrl = file ? URL.createObjectURL(file) : null;
|
||||
updateDirty();
|
||||
}
|
||||
|
||||
function reset() {
|
||||
puppetScale = $sceneConfiguration.puppetScale;
|
||||
puppetOpacity = $sceneConfiguration.puppetOpacity;
|
||||
puppetMovementMode = $sceneConfiguration.puppetMovementMode;
|
||||
clearSkinDraft();
|
||||
dirty = false;
|
||||
error = "";
|
||||
}
|
||||
@@ -50,17 +78,29 @@
|
||||
busy = true;
|
||||
error = "";
|
||||
try {
|
||||
const configuration = await commands.updateSceneConfiguration({
|
||||
puppetScale,
|
||||
puppetOpacity,
|
||||
puppetMovementMode,
|
||||
});
|
||||
puppetScale = configuration.puppetScale;
|
||||
puppetOpacity = configuration.puppetOpacity;
|
||||
puppetMovementMode = configuration.puppetMovementMode;
|
||||
if (skinFile) {
|
||||
const currentProfile = await commands.getProfile();
|
||||
const skinData = Array.from(
|
||||
new Uint8Array(await skinFile.arrayBuffer()),
|
||||
);
|
||||
await commands.updateProfile(currentProfile.displayName, skinData);
|
||||
clearSkinDraft();
|
||||
}
|
||||
|
||||
if (hasConfigurationChanges()) {
|
||||
const configuration = await commands.updateSceneConfiguration({
|
||||
puppetScale,
|
||||
puppetOpacity,
|
||||
puppetMovementMode,
|
||||
});
|
||||
puppetScale = configuration.puppetScale;
|
||||
puppetOpacity = configuration.puppetOpacity;
|
||||
puppetMovementMode = configuration.puppetMovementMode;
|
||||
}
|
||||
dirty = false;
|
||||
return true;
|
||||
} catch (cause) {
|
||||
dirty = hasConfigurationChanges() || skinFile !== null;
|
||||
error = String(cause);
|
||||
return false;
|
||||
} finally {
|
||||
@@ -70,52 +110,117 @@
|
||||
</script>
|
||||
|
||||
<div class="space-y-3">
|
||||
{#if error || $sceneConfigurationListenerError}
|
||||
{#if error || $sceneConfigurationListenerError || $profileListenerError}
|
||||
<PanelMessage kind="error">
|
||||
{error || $sceneConfigurationListenerError}
|
||||
{error || $sceneConfigurationListenerError || $profileListenerError}
|
||||
</PanelMessage>
|
||||
{/if}
|
||||
|
||||
<fieldset class="fieldset border border-base-300 bg-base-100 p-3">
|
||||
<legend class="fieldset-legend px-1">Puppets</legend>
|
||||
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<label class="fieldset-label" for="puppet-scale">Scale</label>
|
||||
<output class="badge badge-outline tabular-nums" for="puppet-scale">
|
||||
{puppetScale.toFixed(2)}×
|
||||
</output>
|
||||
</div>
|
||||
<input
|
||||
id="puppet-scale"
|
||||
class="range range-sm"
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="2"
|
||||
step="0.05"
|
||||
bind:value={puppetScale}
|
||||
oninput={updateDirty}
|
||||
disabled={busy}
|
||||
aria-describedby="puppet-scale-help"
|
||||
/>
|
||||
<div
|
||||
class="grid grid-cols-[minmax(0,3fr)_minmax(0,4fr)] items-center gap-3"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="aspect-square min-w-0">
|
||||
{#if $profile}
|
||||
<div
|
||||
class="border border-primary relative shadow-[inset_0_0_10px] bg-primary/5 shadow-primary card"
|
||||
>
|
||||
<div
|
||||
class="size-full absolute bg-gridded opacity-25 inset-0"
|
||||
></div>
|
||||
<div class="size-full absolute">
|
||||
<div
|
||||
class="flex flex-row size-full items-end justify-between text-[10px] text-primary p-1"
|
||||
>
|
||||
<div class="text-start flex flex-col">
|
||||
<p>Scale</p>
|
||||
<p>Opacity</p>
|
||||
</div>
|
||||
<div class="text-end flex flex-col">
|
||||
<p>{(puppetScale * 100).toFixed(0)}%</p>
|
||||
<p>{(puppetOpacity * 100).toFixed(0)}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="size-full z-10">
|
||||
<PuppetPreview
|
||||
userId={$profile.id}
|
||||
skinHash={$profile.skinHash}
|
||||
skinSource={skinPreviewUrl}
|
||||
scale={puppetScale}
|
||||
opacity={puppetOpacity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="skeleton aspect-square size-full"
|
||||
aria-label="Loading puppet preview"
|
||||
></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-between gap-3">
|
||||
<label class="fieldset-label" for="puppet-opacity">Opacity</label>
|
||||
<output class="badge badge-outline tabular-nums" for="puppet-opacity">
|
||||
{Math.round(puppetOpacity * 100)}%
|
||||
</output>
|
||||
<div class="min-w-0 space-y-2">
|
||||
<div>
|
||||
<label class="fieldset-label" for="puppet-scale">Scale</label>
|
||||
<input
|
||||
id="puppet-scale"
|
||||
class="range range-sm"
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="2"
|
||||
step="0.05"
|
||||
bind:value={puppetScale}
|
||||
oninput={updateDirty}
|
||||
disabled={busy}
|
||||
aria-describedby="puppet-scale-help"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="fieldset-label" for="puppet-opacity">Opacity</label>
|
||||
<input
|
||||
id="puppet-opacity"
|
||||
class="range range-sm"
|
||||
type="range"
|
||||
min="0.25"
|
||||
max="1"
|
||||
step="0.05"
|
||||
bind:value={puppetOpacity}
|
||||
oninput={updateDirty}
|
||||
disabled={busy}
|
||||
aria-describedby="puppet-opacity-help"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="fieldset-label" for="puppet-skin">Custom Skin</label>
|
||||
<div>
|
||||
<button
|
||||
class="btn w-full"
|
||||
type="button"
|
||||
disabled={!$profile || busy}
|
||||
onclick={() => skinInput?.click()}
|
||||
>
|
||||
Choose file
|
||||
</button>
|
||||
<input
|
||||
id="skin-file"
|
||||
class="hidden"
|
||||
type="file"
|
||||
accept="image/png"
|
||||
bind:this={skinInput}
|
||||
onchange={(event) =>
|
||||
selectSkinFile(event.currentTarget.files?.[0] ?? null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
id="puppet-opacity"
|
||||
class="range range-sm"
|
||||
type="range"
|
||||
min="0.25"
|
||||
max="1"
|
||||
step="0.05"
|
||||
bind:value={puppetOpacity}
|
||||
oninput={updateDirty}
|
||||
disabled={busy}
|
||||
aria-describedby="puppet-opacity-help"
|
||||
/>
|
||||
|
||||
<fieldset class="mt-4">
|
||||
<legend class="fieldset-label mb-2">Movement</legend>
|
||||
@@ -185,3 +290,34 @@
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.bg-gridded {
|
||||
background-image:
|
||||
linear-gradient(
|
||||
0deg,
|
||||
transparent 24%,
|
||||
var(--color-primary) 25%,
|
||||
var(--color-primary) 26%,
|
||||
transparent 27%,
|
||||
transparent 74%,
|
||||
var(--color-primary) 75%,
|
||||
var(--color-primary) 76%,
|
||||
transparent 77%,
|
||||
transparent
|
||||
),
|
||||
linear-gradient(
|
||||
90deg,
|
||||
transparent 24%,
|
||||
var(--color-primary) 25%,
|
||||
var(--color-primary) 26%,
|
||||
transparent 27%,
|
||||
transparent 74%,
|
||||
var(--color-primary) 75%,
|
||||
var(--color-primary) 76%,
|
||||
transparent 77%,
|
||||
transparent
|
||||
);
|
||||
background-size: 32px 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user