Skip to content

Commit 93a0c0c

Browse files
committed
Some more work
1 parent aae115f commit 93a0c0c

8 files changed

Lines changed: 214 additions & 24 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Root from "./slider.svelte";
2+
3+
export {
4+
Root,
5+
//
6+
Root as Slider,
7+
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<script lang="ts">
2+
import { Slider as SliderPrimitive } from "bits-ui";
3+
import { cn, type WithoutChildrenOrChild } from "$lib/utils/shadcn.js";
4+
5+
let {
6+
ref = $bindable(null),
7+
value = $bindable(),
8+
orientation = "horizontal",
9+
class: className,
10+
...restProps
11+
}: WithoutChildrenOrChild<SliderPrimitive.RootProps> = $props();
12+
</script>
13+
14+
<!--
15+
Discriminated Unions + Destructing (required for bindable) do not
16+
get along, so we shut typescript up by casting `value` to `never`.
17+
-->
18+
<SliderPrimitive.Root
19+
bind:ref
20+
bind:value={value as never}
21+
data-slot="slider"
22+
{orientation}
23+
class={cn(
24+
"relative flex w-full touch-none select-none items-center data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col data-[disabled]:opacity-50",
25+
className
26+
)}
27+
{...restProps}
28+
>
29+
{#snippet children({ thumbs })}
30+
<span
31+
data-orientation={orientation}
32+
data-slot="slider-track"
33+
class={cn(
34+
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-1.5"
35+
)}
36+
>
37+
<SliderPrimitive.Range
38+
data-slot="slider-range"
39+
class={cn(
40+
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
41+
)}
42+
/>
43+
</span>
44+
{#each thumbs as thumb (thumb)}
45+
<SliderPrimitive.Thumb
46+
data-slot="slider-thumb"
47+
index={thumb}
48+
class="border-primary bg-background ring-ring/50 focus-visible:outline-hidden block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50"
49+
/>
50+
{/each}
51+
{/snippet}
52+
</SliderPrimitive.Root>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Root from "./switch.svelte";
2+
3+
export {
4+
Root,
5+
//
6+
Root as Switch,
7+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script lang="ts">
2+
import { Switch as SwitchPrimitive } from "bits-ui";
3+
import { cn, type WithoutChildrenOrChild } from "$lib/utils/shadcn.js";
4+
5+
let {
6+
ref = $bindable(null),
7+
class: className,
8+
checked = $bindable(false),
9+
...restProps
10+
}: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props();
11+
</script>
12+
13+
<SwitchPrimitive.Root
14+
bind:ref
15+
bind:checked
16+
data-slot="switch"
17+
class={cn(
18+
"data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 shadow-xs peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent outline-none transition-all focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
19+
className
20+
)}
21+
{...restProps}
22+
>
23+
<SwitchPrimitive.Thumb
24+
data-slot="switch-thumb"
25+
class={cn(
26+
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
27+
)}
28+
/>
29+
</SwitchPrimitive.Root>
Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,27 @@
11
<script lang="ts">
2-
import { Pause, Play } from '@lucide/svelte';
32
import { page } from '$app/state';
43
import { publicShockerSharesApi } from '$lib/api';
54
import type { PublicShareResponse } from '$lib/api/internal/v1';
6-
import { Button } from '$lib/components/ui/button';
75
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';
6+
import SharedDevice from './SharedDevice.svelte';
87
9-
let details = $state<PublicShareResponse>();
8+
let details = $state<PublicShareResponse | null>(null);
109
10+
// Fetch share details
1111
$effect(() => {
12-
let shareId = page.params.shareId;
13-
if (shareId === undefined) return;
12+
const shareId = page.params.shareId;
13+
if (!shareId) return;
1414
1515
publicShockerSharesApi
1616
.publicGetPublicShare(shareId)
17-
.then((response) => (details = response.data))
17+
.then((res) => (details = res.data))
1818
.catch(handleApiError);
1919
});
2020
</script>
2121

22-
<ul>
23-
{#each details?.devices ?? [] as device}
24-
{#each device.shockers ?? [] as shocker}
25-
<li class="m-2 flex items-center gap-2">
26-
<Button>
27-
<Pause />
28-
<Play />
29-
</Button>
30-
{shocker.name}
31-
<div class="flex-1"></div>
32-
<Button>Shock</Button>
33-
<Button>Vibrate</Button>
34-
<Button>Sound</Button>
35-
<Button>Remove</Button>
36-
</li>
37-
{/each}
22+
{#if details}
23+
Editing share "{details.name}"
24+
{#each details.devices ?? [] as device}
25+
<SharedDevice {device} />
3826
{/each}
39-
</ul>
27+
{/if}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script lang="ts">
2+
import type { PublicShareDevice } from '$lib/api/internal/v1';
3+
import SharedShocker from './SharedShocker.svelte';
4+
5+
interface Props {
6+
device: PublicShareDevice;
7+
}
8+
9+
let { device }: Props = $props();
10+
</script>
11+
12+
<section class="mt-6">
13+
<h2 class="text-2xl font-semibold mb-4">{device.name}</h2>
14+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
15+
{#each device.shockers ?? [] as shocker}
16+
<SharedShocker {shocker} />
17+
{/each}
18+
</div>
19+
</section>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<script lang="ts">
2+
import { Pause, Play } from '@lucide/svelte';
3+
import type { PublicShareShocker, ShockerPermissions } from '$lib/api/internal/v1';
4+
import { Button } from '$lib/components/ui/button';
5+
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card';
6+
import { Label } from '$lib/components/ui/label';
7+
import { Slider } from '$lib/components/ui/slider';
8+
import { Switch } from '$lib/components/ui/switch';
9+
10+
interface Props {
11+
shocker: PublicShareShocker;
12+
}
13+
14+
let { shocker }: Props = $props();
15+
16+
// UI callbacks (stub implementations)
17+
const togglePlay = async (shockerId: string) => {
18+
// TODO: Call API to toggle play state
19+
};
20+
21+
const toggleFeature = async (shockerId: string, feature: string, enabled: boolean) => {
22+
// TODO: Call API to toggle feature (shock, vibrate, sound, livecontrol, estop)
23+
};
24+
25+
const updateDuration = async (shockerId: string, duration: number) => {
26+
// TODO: Call API to update duration limit
27+
};
28+
29+
const updateIntensity = async (shockerId: string, intensity: number) => {
30+
// TODO: Call API to update intensity limit
31+
};
32+
33+
const removeShocker = async (shockerId: string) => {
34+
// TODO: Call API to remove shocker from share
35+
};
36+
</script>
37+
38+
<Card>
39+
<CardHeader>
40+
<CardTitle class="flex items-center justify-between">
41+
<span class="text-lg font-medium">{shocker.name}</span>
42+
<Button variant="outline" size="sm" onclick={() => togglePlay(shocker.id)}>
43+
{#if shocker.paused}
44+
<Pause size={16} />
45+
{:else}
46+
<Play size={16} />
47+
{/if}
48+
</Button>
49+
</CardTitle>
50+
</CardHeader>
51+
52+
<CardContent class="space-y-4">
53+
<!-- Feature Toggles -->
54+
<div class="space-y-2">
55+
{#each ['shock', 'vibrate', 'sound', 'live'] satisfies (keyof ShockerPermissions)[] as feature}
56+
<div class="flex items-center justify-between">
57+
<Label class="capitalize">{feature}</Label>
58+
<Switch
59+
checked={shocker.permissions[feature]}
60+
onCheckedChange={(e) => toggleFeature(shocker.id, feature, e)}
61+
/>
62+
</div>
63+
{/each}
64+
</div>
65+
66+
<!-- Duration Slider -->
67+
<div class="space-y-1">
68+
<Label>Duration: {shocker.limits.duration}s</Label>
69+
<Slider type="single" value={shocker.limits.duration ?? 0} min={1} max={120} step={1} />
70+
</div>
71+
72+
<!-- Intensity Slider -->
73+
<div class="space-y-1">
74+
<Label>Intensity: {shocker.limits.intensity}%</Label>
75+
<Slider type="single" value={shocker.limits.intensity ?? 0} min={0} max={100} step={5} />
76+
</div>
77+
</CardContent>
78+
79+
<CardFooter>
80+
<Button variant="destructive" size="sm" onclick={() => removeShocker(shocker.id)}>
81+
Remove
82+
</Button>
83+
</CardFooter>
84+
</Card>

src/routes/(authenticated)/shockers/+page.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { Layers, Settings } from '@lucide/svelte';
2+
import { Layers, OctagonAlert, Settings, StopCircle } from '@lucide/svelte';
33
import Container from '$lib/components/Container.svelte';
44
import ClassicControlModule from '$lib/components/ControlModules/ClassicControlModule.svelte';
55
import MapControlModule from '$lib/components/ControlModules/MapControlModule.svelte';
@@ -31,6 +31,10 @@
3131
<div class="w-full flex content-center justify-between">
3232
<h1 class="text-2xl font-bold">Shockers</h1>
3333
<div>
34+
<!-- Emergency Stop Button -->
35+
<Button variant="secondary" class="border-2 text-red-500">
36+
<OctagonAlert size="64" /> STOP
37+
</Button>
3438
<!-- Mode button -->
3539
<Popover.Root>
3640
<Popover.Trigger><Layers /></Popover.Trigger>

0 commit comments

Comments
 (0)