Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/lib/holocene/icon/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import merge from './svg/merge.svelte';
import microchip from './svg/microchip.svelte';
import microsoft from './svg/microsoft.svelte';
import minimize from './svg/minimize.svelte';
import minus from './svg/minus.svelte';
import moon from './svg/moon.svelte';
import namespaceSwitcher from './svg/namespace-switcher.svelte';
import namespace from './svg/namespace.svelte';
Expand All @@ -102,6 +103,7 @@ import pencil from './svg/pencil.svelte';
import pinFilled from './svg/pin-filled.svelte';
import pin from './svg/pin.svelte';
import play from './svg/play.svelte';
import plus from './svg/plus.svelte';
import regions from './svg/regions.svelte';
import relationship from './svg/relationship.svelte';
import retention from './svg/retention.svelte';
Expand Down Expand Up @@ -249,13 +251,15 @@ export const icons = {
microchip,
microsoft,
minimize,
minus,
moon,
'namespace-switcher': namespaceSwitcher,
namespace,
nexus,
'office-buildings': officeBuildings,
overview,
play,
plus,
pause,
pencil,
'pin-filled': pinFilled,
Expand Down
8 changes: 8 additions & 0 deletions src/lib/holocene/icon/svg/minus.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
import Svg from '../svg.svelte';
let props = $props();
</script>

<Svg {...props}>
<path d="M19 13H5V11H19V13Z" fill="currentcolor" />
</Svg>
8 changes: 8 additions & 0 deletions src/lib/holocene/icon/svg/plus.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
import Svg from '../svg.svelte';
let props = $props();
</script>

<Svg {...props}>
<path d="M19 13H13V19H11V13H5V11H11V5H13V11H19V13Z" fill="currentcolor" />
</Svg>
117 changes: 117 additions & 0 deletions src/lib/holocene/zoom-svg.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@
let panOffsetX = 0;
let panOffsetY = 0;

const PAN_STEP_RATIO = 0.1;
const ZOOM_STEP = 0.1;

function panBy(dx: number, dy: number) {
if (!pannable) return;
viewBox.x += dx * viewBox.width;
viewBox.y += dy * viewBox.height;
}

function zoomBy(factor: number, centerX = width / 2, centerY = height / 2) {
if (!zoomable) return;
const newZoomLevel = zoomLevel + factor;
if (newZoomLevel < maxZoomIn || newZoomLevel > maxZoomOut) return;
const zoomRatio = newZoomLevel / zoomLevel;
viewBox.x = centerX - (centerX - viewBox.x) * zoomRatio;
viewBox.y = centerY - (centerY - viewBox.y) * zoomRatio;
viewBox.width *= zoomRatio;
viewBox.height *= zoomRatio;
zoomLevel = newZoomLevel;
}

const handleWheel = (event: WheelEvent) => {
if (!zoomable) return;
event.preventDefault();
Expand Down Expand Up @@ -87,22 +108,118 @@
viewBox.height = height;
zoomLevel = initialZoom;
}

function handleKeydown(event: KeyboardEvent) {
switch (event.key) {
case 'ArrowUp':
event.preventDefault();
panBy(0, -PAN_STEP_RATIO);
break;
case 'ArrowDown':
event.preventDefault();
panBy(0, PAN_STEP_RATIO);
break;
case 'ArrowLeft':
event.preventDefault();
panBy(-PAN_STEP_RATIO, 0);
break;
case 'ArrowRight':
event.preventDefault();
panBy(PAN_STEP_RATIO, 0);
break;
case '+':
case '=':
event.preventDefault();
zoomBy(-ZOOM_STEP);
break;
case '-':
case '_':
event.preventDefault();
zoomBy(ZOOM_STEP);
break;
}
}
</script>

<div
class="relative overflow-hidden"
tabindex="0"
role="group"
aria-label="Zoomable workflow graph. Use arrow keys to pan, plus and minus to zoom."
on:keydown={handleKeydown}
bind:clientWidth={width}
bind:clientHeight={height}
style="height: min({containerHeight}px, calc(100dvh - 8rem));"
>
<div class="absolute right-4 top-4 z-20 flex items-center gap-2">
<slot name="controls" />
{#if pannable}
<Tooltip text="Pan up" bottom>
<Button
variant="secondary"
size="sm"
leadingIcon="chevron-up"
aria-label="Pan up"
on:click={() => panBy(0, -PAN_STEP_RATIO)}
/>
</Tooltip>
<Tooltip text="Pan down" bottom>
<Button
variant="secondary"
size="sm"
leadingIcon="chevron-down"
aria-label="Pan down"
on:click={() => panBy(0, PAN_STEP_RATIO)}
/>
</Tooltip>
<Tooltip text="Pan left" bottom>
<Button
variant="secondary"
size="sm"
leadingIcon="chevron-left"
aria-label="Pan left"
on:click={() => panBy(-PAN_STEP_RATIO, 0)}
/>
</Tooltip>
<Tooltip text="Pan right" bottom>
<Button
variant="secondary"
size="sm"
leadingIcon="chevron-right"
aria-label="Pan right"
on:click={() => panBy(PAN_STEP_RATIO, 0)}
/>
</Tooltip>
{/if}
{#if zoomable}
<Tooltip text="Zoom in" bottom>
<Button
variant="secondary"
size="sm"
leadingIcon="plus"
aria-label="Zoom in"
disabled={zoomLevel - ZOOM_STEP < maxZoomIn}
on:click={() => zoomBy(-ZOOM_STEP)}
/>
</Tooltip>
<Tooltip text="Zoom out" bottom>
<Button
variant="secondary"
size="sm"
leadingIcon="minus"
aria-label="Zoom out"
disabled={zoomLevel + ZOOM_STEP > maxZoomOut}
on:click={() => zoomBy(ZOOM_STEP)}
/>
</Tooltip>
{/if}
<Tooltip text="Center" bottom>
<Button
class="cursor-pointer"
variant="secondary"
size="sm"
leadingIcon="target"
aria-label="Center"
on:click={() => {
onCenter();
}}
Expand Down
Loading