⛹️♂️ Simplifies layout handling
This commit is contained in:
@@ -1,14 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { user } from '$lib/stores/user';
|
import { user } from '$lib/stores/user';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
import { location } from '$lib/utilities';
|
||||||
|
|
||||||
const ws_token = `?access_token=${$user.bearer_token}`
|
let source = `//${location}/api/camera/stream?access_token=${$user.bearer_token}`;
|
||||||
|
|
||||||
let source = "/api/camera/stream"+ ws_token;
|
onDestroy(() => (source = '#'));
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
source = '#';
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import { persistentStore } from '$lib/utilities';
|
|
||||||
import Visualization from '$lib/components/Visualization.svelte';
|
|
||||||
import Stream from '$lib/components/Stream.svelte';
|
|
||||||
import CpuUsageChart from '$lib/components/widget/CpuUsageChart.svelte';
|
|
||||||
import type { Writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export type SizeUnit = 'px' | '%' | 'fr' | 'em';
|
|
||||||
|
|
||||||
export enum SizeUnitEnum {
|
|
||||||
Em = 'em',
|
|
||||||
Fractional = 'fr',
|
|
||||||
Percent = '%',
|
|
||||||
Pixel = 'px'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WidgetConfig {
|
|
||||||
id: string | number;
|
|
||||||
component: keyof typeof WidgetComponents;
|
|
||||||
size?: number;
|
|
||||||
sizeUnit?: SizeUnitEnum;
|
|
||||||
props?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WidgetContainerConfig {
|
|
||||||
id: string | number;
|
|
||||||
layout: 'row' | 'column' | 'wrap';
|
|
||||||
header?: string;
|
|
||||||
size?: number;
|
|
||||||
sizeUnit?: SizeUnitEnum;
|
|
||||||
widgets: Array<WidgetConfig | WidgetContainerConfig>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isWidgetConfig = (
|
|
||||||
widget: WidgetConfig | WidgetContainerConfig
|
|
||||||
): widget is WidgetConfig => 'component' in widget;
|
|
||||||
|
|
||||||
export const WidgetComponents = {
|
|
||||||
Visualization,
|
|
||||||
Stream,
|
|
||||||
CpuUsageChart
|
|
||||||
};
|
|
||||||
|
|
||||||
export const phoneControllerLayout: Writable<WidgetContainerConfig> = persistentStore(
|
|
||||||
'phone_controller_layout',
|
|
||||||
{
|
|
||||||
id: 'root',
|
|
||||||
layout: 'wrap',
|
|
||||||
widgets: [
|
|
||||||
{ id: 2, component: 'Stream' },
|
|
||||||
{ id: 1, component: 'Visualization', props: { debug: true } }
|
|
||||||
]
|
|
||||||
} as WidgetContainerConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
export const controllerLayout: Writable<WidgetContainerConfig> = persistentStore('controller_layout', {
|
|
||||||
id: 'root',
|
|
||||||
layout: 'column',
|
|
||||||
widgets: [
|
|
||||||
{
|
|
||||||
id: 'visualization',
|
|
||||||
layout: 'column',
|
|
||||||
header: 'Visualization',
|
|
||||||
widgets: [
|
|
||||||
{ id: 1, component: 'Stream' },
|
|
||||||
{ id: 2, component: 'Visualization', props: { debug: true } }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'charts',
|
|
||||||
layout: 'row',
|
|
||||||
size: 40,
|
|
||||||
header: 'Charts',
|
|
||||||
widgets: [
|
|
||||||
{ id: 3, component: 'CpuUsageChart' },
|
|
||||||
{ id: 4, component: 'CpuUsageChart' },
|
|
||||||
{ id: 5, component: 'CpuUsageChart' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} as WidgetContainerConfig);
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SizeUnit } from "./LayoutManager";
|
|
||||||
|
|
||||||
export let size = -1;
|
|
||||||
export let sizeUnit:SizeUnit = '%';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="box-border overflow-hidden min-w-96 "class:flex-1={size === -1}>
|
<div class="box-border overflow-hidden flex-1">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-9
@@ -1,18 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WidgetComponents, type WidgetContainerConfig, isWidgetConfig } from './LayoutManager';
|
import { WidgetComponents, type WidgetContainerConfig, isWidgetConfig } from '$lib/stores/application';
|
||||||
import Widget from './Widget.svelte';
|
import Widget from './Widget.svelte';
|
||||||
|
|
||||||
export let container: WidgetContainerConfig;
|
export let container: WidgetContainerConfig;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full h-full flex flex-col overflow-hidden">
|
<div class="w-full h-full flex flex-col overflow-hidden">
|
||||||
{#if container.header}
|
|
||||||
<div role="tablist" class="tabs tabs-lifted w-min">
|
|
||||||
<a role="tab" class="tab">{container.header}</a>
|
|
||||||
<a role="tab" class="tab tab-active">{container.header}</a>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex w-full h-full"
|
class="flex w-full h-full"
|
||||||
class:flex-row={container.layout === 'column'}
|
class:flex-row={container.layout === 'column'}
|
||||||
@@ -20,7 +13,7 @@
|
|||||||
class:flex-wrap={container.layout === 'wrap'}
|
class:flex-wrap={container.layout === 'wrap'}
|
||||||
>
|
>
|
||||||
{#each container.widgets as widget, index (widget.id + '-' + index)}
|
{#each container.widgets as widget, index (widget.id + '-' + index)}
|
||||||
<Widget size={widget.size} sizeUnit={widget.sizeUnit}>
|
<Widget>
|
||||||
{#if isWidgetConfig(widget)}
|
{#if isWidgetConfig(widget)}
|
||||||
<svelte:component this={WidgetComponents[widget.component]} {...widget.props} />
|
<svelte:component this={WidgetComponents[widget.component]} {...widget.props} />
|
||||||
{:else if widget.widgets}
|
{:else if widget.widgets}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import ChartBase from "./ChartBase.svelte";
|
|
||||||
|
|
||||||
let CpuData:number[] = [1,2,3,4,5,6,7,8,9,]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<ChartBase data={CpuData} label="CPU usage" title="CPU usage"></ChartBase>
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let options: string[] = [];
|
||||||
|
export let selectedOption: string = '';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<select bind:value={selectedOption} on:change class="select select-bordered sm:select-sm max-w-xs {$$restProps.class || ''}">
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option}>{option}</option>
|
||||||
|
{/each}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { persistentStore } from '$lib/utilities';
|
||||||
|
import { get, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
|
import Visualization from '$lib/components/Visualization.svelte';
|
||||||
|
import Stream from '$lib/components/Stream.svelte';
|
||||||
|
import ChartWidget from '$lib/components/widget/ChartWidget.svelte';
|
||||||
|
|
||||||
|
export interface WidgetConfig {
|
||||||
|
id: string | number;
|
||||||
|
component: keyof typeof WidgetComponents;
|
||||||
|
props?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WidgetContainerConfig {
|
||||||
|
id: string | number;
|
||||||
|
layout?: 'row' | 'column' | 'wrap';
|
||||||
|
header?: string;
|
||||||
|
widgets: Array<WidgetConfig | WidgetContainerConfig>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isWidgetConfig = (
|
||||||
|
widget: WidgetConfig | WidgetContainerConfig
|
||||||
|
): widget is WidgetConfig => 'component' in widget;
|
||||||
|
|
||||||
|
export const WidgetComponents = {
|
||||||
|
Visualization,
|
||||||
|
Stream,
|
||||||
|
ChartWidget
|
||||||
|
};
|
||||||
|
|
||||||
|
interface View {
|
||||||
|
name: string;
|
||||||
|
content: WidgetContainerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultViews: View[] = [
|
||||||
|
{
|
||||||
|
name: 'Stream',
|
||||||
|
content: {
|
||||||
|
id: 'root',
|
||||||
|
layout: 'column',
|
||||||
|
widgets: [{ id: 2, component: 'Stream' }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '3D representation',
|
||||||
|
content: {
|
||||||
|
id: 'root',
|
||||||
|
layout: 'column',
|
||||||
|
widgets: [{ id: 2, component: 'Visualization', props: { debug: true } }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Split screen',
|
||||||
|
content: {
|
||||||
|
id: 'root',
|
||||||
|
widgets: [
|
||||||
|
{ id: 2, component: 'Stream' },
|
||||||
|
{ id: 2, component: 'Visualization', props: { debug: true } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const views: Writable<View[]> = persistentStore('views', defaultViews);
|
||||||
|
|
||||||
|
export const selectedView = persistentStore('selected_view', get(views)[0].name);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export const isFullscreen = writable(false);
|
||||||
|
|
||||||
|
export function toggleFullscreen() {
|
||||||
|
isFullscreen.update((state) => {
|
||||||
|
!state ? document.documentElement.requestFullscreen() : document.exitFullscreen();
|
||||||
|
return !state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enterFullscreen() {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
document.documentElement.requestFullscreen();
|
||||||
|
isFullscreen.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exitFullscreen() {
|
||||||
|
if (document.fullscreenElement) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
isFullscreen.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ export * from './socket-store';
|
|||||||
export * from './logging-store';
|
export * from './logging-store';
|
||||||
export * from './model-store';
|
export * from './model-store';
|
||||||
export * from './socket';
|
export * from './socket';
|
||||||
|
export * from './fullscreen';
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Controls from './Controls.svelte';
|
import Controls from './Controls.svelte';
|
||||||
import WidgetContainer from '$lib/components/layout/DynamicLayout.svelte';
|
import WidgetContainer from '$lib/components/layout/WidgetContainer.svelte';
|
||||||
import { controllerLayout, phoneControllerLayout} from '$lib/components/layout/LayoutManager';
|
import { selectedView, views } from '$lib/stores/application';
|
||||||
|
|
||||||
|
$: layout = $views.find(v => v.name === $selectedView)!
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute top-0 select-none w-screen h-screen">
|
<div class="absolute top-0 select-none w-screen h-screen">
|
||||||
<Controls />
|
<Controls />
|
||||||
<!-- <button class="absolute z-20 btn" on:click={addWidget}>Add Widget</button> -->
|
|
||||||
<div class="absolute w-full h-screen top-0 overflow-hidden lg:pt-16 pt-12">
|
<div class="absolute w-full h-screen top-0 overflow-hidden lg:pt-16 pt-12">
|
||||||
<WidgetContainer container={$controllerLayout} />
|
<WidgetContainer container={layout.content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onDestroy } from 'svelte';
|
|
||||||
import { location } from '$lib/utilities';
|
|
||||||
|
|
||||||
let videoStream = `//${location}/api/stream`;
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
videoStream = '#';
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="w-full h-full">
|
|
||||||
<img
|
|
||||||
src={videoStream}
|
|
||||||
class="absolute object-cover blur-3xl w-full h-full -z-10"
|
|
||||||
alt="Live stream is down"
|
|
||||||
/>
|
|
||||||
<img src={videoStream} class="object-contain w-full h-full" alt="Live stream is down" />
|
|
||||||
</div>
|
|
||||||
@@ -15,9 +15,11 @@
|
|||||||
import MdiFullscreen from '~icons/mdi/fullscreen';
|
import MdiFullscreen from '~icons/mdi/fullscreen';
|
||||||
import MdiFullscreenExit from '~icons/mdi/fullscreen-exit';
|
import MdiFullscreenExit from '~icons/mdi/fullscreen-exit';
|
||||||
import { api } from '$lib/api';
|
import { api } from '$lib/api';
|
||||||
import { mode, modes } from '$lib/stores';
|
import { isFullscreen, mode, modes, toggleFullscreen } from '$lib/stores';
|
||||||
|
import Selector from '$lib/components/widget/Selector.svelte';
|
||||||
|
import { selectedView, views } from '$lib/stores/application';
|
||||||
|
|
||||||
const postSleep = async () => await api.post('/api/sleep')
|
const postSleep = async () => await api.post('/api/sleep');
|
||||||
|
|
||||||
function confirmSleep() {
|
function confirmSleep() {
|
||||||
openModal(ConfirmDialog, {
|
openModal(ConfirmDialog, {
|
||||||
@@ -36,42 +38,34 @@
|
|||||||
|
|
||||||
const deactivate = async () => {
|
const deactivate = async () => {
|
||||||
mode.set(modes.indexOf('deactivated'));
|
mode.set(modes.indexOf('deactivated'));
|
||||||
}
|
};
|
||||||
|
|
||||||
let isFullscreen = false;
|
|
||||||
|
|
||||||
function toggleFullScreen() {
|
|
||||||
isFullscreen = !isFullscreen
|
|
||||||
if (!document.fullscreenElement) {
|
|
||||||
document.documentElement.requestFullscreen();
|
|
||||||
} else if (document.exitFullscreen) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="navbar bg-base-300 sticky top-0 z-10 h-12 min-h-fit drop-shadow-lg lg:h-16 gap-2 pr-0">
|
<div class="navbar bg-base-300 sticky top-0 z-10 h-12 min-h-fit drop-shadow-lg lg:h-16 gap-2 pr-0">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<!-- Page Hamburger Icon here -->
|
|
||||||
<label for="main-menu" class="btn btn-ghost btn-circle btn-sm drawer-button"
|
<label for="main-menu" class="btn btn-ghost btn-circle btn-sm drawer-button"
|
||||||
><Hamburger class="h-6 w-auto" /></label
|
><Hamburger class="h-6 w-auto" /></label
|
||||||
>
|
>
|
||||||
|
{#if $page.data.title === 'Controller'}
|
||||||
|
<Selector bind:selectedOption={$selectedView} options={$views.map((v) => v.name)} />
|
||||||
|
{:else}
|
||||||
<h1 class="px-2 text-xl font-bold lg:text-2xl">{$page.data.title}</h1>
|
<h1 class="px-2 text-xl font-bold lg:text-2xl">{$page.data.title}</h1>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="indicator flex-none">
|
<div class="indicator flex-none">
|
||||||
<UpdateIndicator />
|
<UpdateIndicator />
|
||||||
</div>
|
</div>
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="flex-none" on:click={toggleFullScreen}>
|
<div class="flex-none" on:click={toggleFullscreen}>
|
||||||
<svelte:component this={isFullscreen ? MdiFullscreenExit : MdiFullscreen} class="h-7 w-7"/>
|
<svelte:component this={isFullscreen ? MdiFullscreenExit : MdiFullscreen} class="h-7 w-7" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
<label class="swap swap-rotate">
|
<label class="swap swap-rotate">
|
||||||
<input type="checkbox" value="light" class="theme-controller"/>
|
<input type="checkbox" value="light" class="theme-controller" />
|
||||||
<MdiWeatherSunny class="swap-off h-7 w-7"/>
|
<MdiWeatherSunny class="swap-off h-7 w-7" />
|
||||||
<MdiMoonAndStars class="swap-on h-7 w-7"/>
|
<MdiMoonAndStars class="swap-on h-7 w-7" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
|
|||||||
Reference in New Issue
Block a user