option to hide own puppet when no friends are online
This commit is contained in:
@@ -158,6 +158,7 @@
|
||||
"scene_all_puppets": "All puppets",
|
||||
"scene_scale": "Scale",
|
||||
"scene_opacity": "Opacity",
|
||||
"scene_hide_local_puppet_when_alone": "Hide my puppet when no friends are online",
|
||||
"scene_choose_skin": "Choose a skin",
|
||||
"scene_loading_preview": "Loading puppet preview",
|
||||
"action_add_friend_title": "Add Friend",
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
"scene_all_puppets": "所有玩偶",
|
||||
"scene_scale": "缩放",
|
||||
"scene_opacity": "不透明度",
|
||||
"scene_hide_local_puppet_when_alone": "没有好友在线时隐藏我的玩偶",
|
||||
"scene_choose_skin": "选择皮肤",
|
||||
"scene_loading_preview": "正在加载玩偶预览",
|
||||
"action_add_friend_title": "添加好友",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE scene_configuration
|
||||
ADD COLUMN hide_local_puppet_when_alone BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
@@ -26,6 +26,7 @@ pub struct SceneConfiguration {
|
||||
pub puppet_scale: f64,
|
||||
pub puppet_opacity: f64,
|
||||
pub puppet_movement_mode: PuppetMovementMode,
|
||||
pub hide_local_puppet_when_alone: bool,
|
||||
}
|
||||
|
||||
pub struct SceneConfigurationState(RwLock<SceneConfiguration>);
|
||||
@@ -56,7 +57,7 @@ pub struct SceneConfigurationChanged {
|
||||
|
||||
async fn get(database: &AppDatabase) -> Result<SceneConfiguration, sqlx::Error> {
|
||||
sqlx::query_as::<_, SceneConfiguration>(
|
||||
"SELECT puppet_scale, puppet_opacity, puppet_movement_mode FROM scene_configuration WHERE id = ?1",
|
||||
"SELECT puppet_scale, puppet_opacity, puppet_movement_mode, hide_local_puppet_when_alone FROM scene_configuration WHERE id = ?1",
|
||||
)
|
||||
.bind(SCENE_CONFIGURATION_ID)
|
||||
.fetch_one(database.pool())
|
||||
@@ -83,11 +84,12 @@ async fn update(
|
||||
}
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE scene_configuration SET puppet_scale = ?1, puppet_opacity = ?2, puppet_movement_mode = ?3 WHERE id = ?4",
|
||||
"UPDATE scene_configuration SET puppet_scale = ?1, puppet_opacity = ?2, puppet_movement_mode = ?3, hide_local_puppet_when_alone = ?4 WHERE id = ?5",
|
||||
)
|
||||
.bind(configuration.puppet_scale)
|
||||
.bind(configuration.puppet_opacity)
|
||||
.bind(configuration.puppet_movement_mode)
|
||||
.bind(configuration.hide_local_puppet_when_alone)
|
||||
.bind(SCENE_CONFIGURATION_ID)
|
||||
.execute(database.pool())
|
||||
.await
|
||||
@@ -164,6 +166,7 @@ mod tests {
|
||||
puppet_scale: 1.0,
|
||||
puppet_opacity: 1.0,
|
||||
puppet_movement_mode: PuppetMovementMode::Free,
|
||||
hide_local_puppet_when_alone: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -178,6 +181,7 @@ mod tests {
|
||||
puppet_scale: 1.5,
|
||||
puppet_opacity: 0.4,
|
||||
puppet_movement_mode: PuppetMovementMode::Bottom,
|
||||
hide_local_puppet_when_alone: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -188,6 +192,7 @@ mod tests {
|
||||
configuration.puppet_movement_mode,
|
||||
PuppetMovementMode::Bottom
|
||||
);
|
||||
assert!(configuration.hide_local_puppet_when_alone);
|
||||
assert_eq!(get(&database).await.unwrap(), configuration);
|
||||
|
||||
let error = update(
|
||||
@@ -196,6 +201,7 @@ mod tests {
|
||||
puppet_scale: 2.1,
|
||||
puppet_opacity: 0.4,
|
||||
puppet_movement_mode: PuppetMovementMode::Bottom,
|
||||
hide_local_puppet_when_alone: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -209,6 +215,7 @@ mod tests {
|
||||
puppet_scale: 1.5,
|
||||
puppet_opacity: 0.05,
|
||||
puppet_movement_mode: PuppetMovementMode::Bottom,
|
||||
hide_local_puppet_when_alone: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ export type PuppetStatesChanged = { puppets: PuppetState[] }
|
||||
export type Remote = { id: string; address: string; name: string | null; port: number | null; priority: number }
|
||||
export type RemoteInput = { address: string; name: string | null; port: number | null }
|
||||
export type RemotesChanged = { remotes: Remote[] }
|
||||
export type SceneConfiguration = { puppetScale: number; puppetOpacity: number; puppetMovementMode: PuppetMovementMode }
|
||||
export type SceneConfiguration = { puppetScale: number; puppetOpacity: number; puppetMovementMode: PuppetMovementMode; hideLocalPuppetWhenAlone: boolean }
|
||||
export type SceneConfigurationChanged = { configuration: SceneConfiguration }
|
||||
export type SceneHitbox = { x: number; y: number; width: number; height: number }
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { commands, type PuppetMovementMode } from "$lib/bindings";
|
||||
import { commands, type SceneConfiguration } from "$lib/bindings";
|
||||
import {
|
||||
sceneConfiguration,
|
||||
sceneConfigurationListenerError,
|
||||
@@ -13,9 +13,12 @@
|
||||
|
||||
let { register }: { register: RegisterPanel } = $props();
|
||||
|
||||
let puppetScale = $state(1);
|
||||
let puppetOpacity = $state(1);
|
||||
let puppetMovementMode = $state<PuppetMovementMode>("free");
|
||||
let configurationDraft = $state<SceneConfiguration>({
|
||||
puppetScale: 1,
|
||||
puppetOpacity: 1,
|
||||
puppetMovementMode: "free",
|
||||
hideLocalPuppetWhenAlone: false,
|
||||
});
|
||||
let skinFile = $state<File | null>(null);
|
||||
let skinPreviewUrl = $state<string | null>(null);
|
||||
let skinInput = $state<HTMLInputElement | null>(null);
|
||||
@@ -26,9 +29,7 @@
|
||||
|
||||
$effect(() => {
|
||||
if (!dirty) {
|
||||
puppetScale = $sceneConfiguration.puppetScale;
|
||||
puppetOpacity = $sceneConfiguration.puppetOpacity;
|
||||
puppetMovementMode = $sceneConfiguration.puppetMovementMode;
|
||||
configurationDraft = { ...$sceneConfiguration };
|
||||
}
|
||||
register("scene", { apply, reset }, { dirty, busy });
|
||||
});
|
||||
@@ -40,9 +41,12 @@
|
||||
|
||||
function hasConfigurationChanges() {
|
||||
return (
|
||||
puppetScale !== $sceneConfiguration.puppetScale ||
|
||||
puppetOpacity !== $sceneConfiguration.puppetOpacity ||
|
||||
puppetMovementMode !== $sceneConfiguration.puppetMovementMode
|
||||
configurationDraft.puppetScale !== $sceneConfiguration.puppetScale ||
|
||||
configurationDraft.puppetOpacity !== $sceneConfiguration.puppetOpacity ||
|
||||
configurationDraft.puppetMovementMode !==
|
||||
$sceneConfiguration.puppetMovementMode ||
|
||||
configurationDraft.hideLocalPuppetWhenAlone !==
|
||||
$sceneConfiguration.hideLocalPuppetWhenAlone
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,9 +77,7 @@
|
||||
}
|
||||
|
||||
function reset() {
|
||||
puppetScale = $sceneConfiguration.puppetScale;
|
||||
puppetOpacity = $sceneConfiguration.puppetOpacity;
|
||||
puppetMovementMode = $sceneConfiguration.puppetMovementMode;
|
||||
configurationDraft = { ...$sceneConfiguration };
|
||||
clearSkinDraft();
|
||||
pendingSkinReset = false;
|
||||
dirty = false;
|
||||
@@ -102,13 +104,9 @@
|
||||
|
||||
if (hasConfigurationChanges()) {
|
||||
const configuration = await commands.updateSceneConfiguration({
|
||||
puppetScale,
|
||||
puppetOpacity,
|
||||
puppetMovementMode,
|
||||
...configurationDraft,
|
||||
});
|
||||
puppetScale = configuration.puppetScale;
|
||||
puppetOpacity = configuration.puppetOpacity;
|
||||
puppetMovementMode = configuration.puppetMovementMode;
|
||||
configurationDraft = { ...configuration };
|
||||
}
|
||||
dirty = false;
|
||||
return true;
|
||||
@@ -149,8 +147,8 @@
|
||||
<p>{$messages.scene_opacity()}</p>
|
||||
</div>
|
||||
<div class="text-end flex flex-col">
|
||||
<p>{(puppetScale * 100).toFixed(0)}%</p>
|
||||
<p>{(puppetOpacity * 100).toFixed(0)}%</p>
|
||||
<p>{(configurationDraft.puppetScale * 100).toFixed(0)}%</p>
|
||||
<p>{(configurationDraft.puppetOpacity * 100).toFixed(0)}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,8 +157,8 @@
|
||||
userId={$profile.id}
|
||||
skinHash={pendingSkinReset ? null : $profile.skinHash}
|
||||
skinSource={skinPreviewUrl}
|
||||
scale={puppetScale}
|
||||
opacity={puppetOpacity}
|
||||
scale={configurationDraft.puppetScale}
|
||||
opacity={configurationDraft.puppetOpacity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,7 +187,7 @@
|
||||
min="0.5"
|
||||
max="2"
|
||||
step="0.05"
|
||||
bind:value={puppetScale}
|
||||
bind:value={configurationDraft.puppetScale}
|
||||
oninput={updateDirty}
|
||||
disabled={busy}
|
||||
aria-describedby="puppet-scale-help"
|
||||
@@ -207,12 +205,23 @@
|
||||
min="0.25"
|
||||
max="1"
|
||||
step="0.05"
|
||||
bind:value={puppetOpacity}
|
||||
bind:value={configurationDraft.puppetOpacity}
|
||||
oninput={updateDirty}
|
||||
disabled={busy}
|
||||
aria-describedby="puppet-opacity-help"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label class="label cursor-pointer justify-start gap-2">
|
||||
<input
|
||||
class="checkbox checkbox-sm checkbox-primary"
|
||||
type="checkbox"
|
||||
bind:checked={configurationDraft.hideLocalPuppetWhenAlone}
|
||||
onchange={updateDirty}
|
||||
disabled={busy}
|
||||
/>
|
||||
<span>{$messages.scene_hide_local_puppet_when_alone()}</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<div class="grid grid-cols-[minmax(0,1fr)_auto] gap-2">
|
||||
<button
|
||||
@@ -247,19 +256,21 @@
|
||||
<legend class="fieldset-legend px-1">{$messages.puppet_movement()}</legend>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label
|
||||
class:border-primary={puppetMovementMode === "free"}
|
||||
class:border-primary={configurationDraft.puppetMovementMode === "free"}
|
||||
class="card cursor-pointer overflow-hidden border border-base-300 bg-base-200"
|
||||
>
|
||||
<img
|
||||
src="/puppet-movement-free.svg"
|
||||
alt={$messages.puppet_free_alt()}
|
||||
class="w-full object-cover bg-linear-to-b from-base-100 to-base-300"
|
||||
class:from-primary-content={puppetMovementMode === "free"}
|
||||
class:from-primary-content={configurationDraft.puppetMovementMode ===
|
||||
"free"}
|
||||
/>
|
||||
<span
|
||||
class="card-body items-center gap-2 p-2 text-center"
|
||||
class:bg-primary-content={puppetMovementMode === "free"}
|
||||
class:text-primary={puppetMovementMode === "free"}
|
||||
class:bg-primary-content={configurationDraft.puppetMovementMode ===
|
||||
"free"}
|
||||
class:text-primary={configurationDraft.puppetMovementMode === "free"}
|
||||
>
|
||||
<span class="text-xs font-medium">{$messages.puppet_free()}</span>
|
||||
<input
|
||||
@@ -267,9 +278,9 @@
|
||||
type="radio"
|
||||
name="puppet-movement-mode"
|
||||
value="free"
|
||||
checked={puppetMovementMode === "free"}
|
||||
checked={configurationDraft.puppetMovementMode === "free"}
|
||||
onchange={() => {
|
||||
puppetMovementMode = "free";
|
||||
configurationDraft.puppetMovementMode = "free";
|
||||
updateDirty();
|
||||
}}
|
||||
disabled={busy}
|
||||
@@ -278,19 +289,22 @@
|
||||
</label>
|
||||
|
||||
<label
|
||||
class:border-primary={puppetMovementMode === "bottom"}
|
||||
class:text-primary={puppetMovementMode === "bottom"}
|
||||
class:border-primary={configurationDraft.puppetMovementMode ===
|
||||
"bottom"}
|
||||
class:text-primary={configurationDraft.puppetMovementMode === "bottom"}
|
||||
class="card cursor-pointer overflow-hidden border border-base-300 bg-base-200"
|
||||
>
|
||||
<img
|
||||
src="/puppet-movement-bottom.svg"
|
||||
alt={$messages.puppet_bottom_alt()}
|
||||
class="w-full object-cover bg-linear-to-b from-base-100 to-base-300"
|
||||
class:from-primary-content={puppetMovementMode === "bottom"}
|
||||
class:from-primary-content={configurationDraft.puppetMovementMode ===
|
||||
"bottom"}
|
||||
/>
|
||||
<span
|
||||
class="card-body items-center gap-2 p-2 text-center"
|
||||
class:bg-primary-content={puppetMovementMode === "bottom"}
|
||||
class:bg-primary-content={configurationDraft.puppetMovementMode ===
|
||||
"bottom"}
|
||||
>
|
||||
<span class="text-xs font-medium">{$messages.puppet_bottom()}</span>
|
||||
<input
|
||||
@@ -298,9 +312,9 @@
|
||||
type="radio"
|
||||
name="puppet-movement-mode"
|
||||
value="bottom"
|
||||
checked={puppetMovementMode === "bottom"}
|
||||
checked={configurationDraft.puppetMovementMode === "bottom"}
|
||||
onchange={() => {
|
||||
puppetMovementMode = "bottom";
|
||||
configurationDraft.puppetMovementMode = "bottom";
|
||||
updateDirty();
|
||||
}}
|
||||
disabled={busy}
|
||||
|
||||
@@ -9,6 +9,7 @@ export const sceneConfiguration = writable<SceneConfiguration>({
|
||||
puppetScale: 1,
|
||||
puppetOpacity: 1,
|
||||
puppetMovementMode: "free",
|
||||
hideLocalPuppetWhenAlone: false,
|
||||
});
|
||||
export const sceneConfigurationListenerError = writable("");
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
puppetScale: 1,
|
||||
puppetOpacity: 1,
|
||||
puppetMovementMode: "free",
|
||||
hideLocalPuppetWhenAlone: false,
|
||||
});
|
||||
let remediation = $state(false);
|
||||
let stepIndex = $state(0);
|
||||
|
||||
@@ -17,17 +17,23 @@
|
||||
import SceneUserPopoverContent from "./popovers/user-interaction.svelte";
|
||||
import { messages } from "$lib/i18n";
|
||||
|
||||
const LOCAL_PUPPET_HIDE_DELAY_MS = 5_000;
|
||||
const LOCAL_PUPPET_SHOW_DELAY_MS = 3_000;
|
||||
|
||||
let selectedUserId = $state<string | null>(null);
|
||||
let lockedPopoverUserId = $state<string | null>(null);
|
||||
let viewedImage = $state<{ source: string; senderName: string } | null>(null);
|
||||
let puppetBounds = $state<PuppetScreenBounds[]>([]);
|
||||
let showLocalPuppet = $state(true);
|
||||
let puppetBoundsById = $derived(
|
||||
new Map(puppetBounds.map((bounds) => [bounds.id, bounds])),
|
||||
);
|
||||
let visiblePuppets = $derived(
|
||||
$puppetStates.filter(
|
||||
({ id }) =>
|
||||
id === $liveMetadata.localId || $onlineFriendIds.has(id),
|
||||
$onlineFriendIds.has(id) ||
|
||||
(id === $liveMetadata.localId &&
|
||||
(!$sceneConfiguration.hideLocalPuppetWhenAlone || showLocalPuppet)),
|
||||
),
|
||||
);
|
||||
let skinHashes = $derived(
|
||||
@@ -45,6 +51,23 @@
|
||||
void getCurrentWindow().setTitle($messages.scene_window_title());
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!$sceneConfiguration.hideLocalPuppetWhenAlone) {
|
||||
showLocalPuppet = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const nextShowLocalPuppet = $onlineFriendIds.size > 0;
|
||||
if (showLocalPuppet === nextShowLocalPuppet) return;
|
||||
const timer = window.setTimeout(
|
||||
() => (showLocalPuppet = nextShowLocalPuppet),
|
||||
nextShowLocalPuppet
|
||||
? LOCAL_PUPPET_SHOW_DELAY_MS
|
||||
: LOCAL_PUPPET_HIDE_DELAY_MS,
|
||||
);
|
||||
return () => window.clearTimeout(timer);
|
||||
});
|
||||
|
||||
onMount(startHitboxSync);
|
||||
|
||||
$effect(() => {
|
||||
|
||||
Reference in New Issue
Block a user