Skip to content

Commit

Permalink
feat: Panzoom support for preview
Browse files Browse the repository at this point in the history
This closes #5
  • Loading branch information
aklinker1 committed Apr 4, 2024
1 parent a176960 commit a0ab3d9
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 59 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"dependencies": {
"@aklinker1/check": "^1.3.1",
"panzoom": "^9.4.3",
"standard-version": "^9.5.0",
"vue-tsc": "^2.0.10"
}
Expand Down
67 changes: 40 additions & 27 deletions web/components/CutlistPreview.vue
Original file line number Diff line number Diff line change
@@ -1,37 +1,50 @@
<script lang="ts" setup>
const { data, isLoading, error } = useBoardLayoutsQuery();
const container = ref<HTMLDivElement>();
const { scale, resetZoom, zoomIn, zoomOut } = usePanZoom(container);
</script>

<template>
<div class="flex flex-col dots-bg">
<p v-if="error">{{ error }}</p>
<template v-else-if="data">
<p
v-if="data.layouts.length === 0"
class="m-auto bg-gray-900 shadow-2xl shadow-gray-900 p-4"
>
No board layouts found
</p>
<LayoutList v-else :layouts="data.layouts" />
</template>
<div>
<!-- Cutlist Preview -->
<div class="absolute inset-0 overflow-none">
<p v-if="error" class="m-auto">{{ error }}</p>

<template v-else-if="data">
<p
v-if="data.layouts.length === 0"
class="m-auto bg-gray-900 shadow-2xl shadow-gray-900 p-4"
>
No board layouts found
</p>
<div v-else ref="container">
<div class="flex flex-col">
<LayoutList :layouts="data.layouts" />
</div>
</div>
</template>

<div v-else-if="isLoading" class="m-auto flex items-center space-x-4">
<USkeleton class="h-12 w-12" :ui="{ rounded: 'rounded-full' }" />
<div class="space-y-2">
<USkeleton class="h-4 w-[250px]" />
<USkeleton class="h-4 w-[200px]" />
<div v-else-if="isLoading" class="m-auto flex items-center space-x-4">
<USkeleton class="h-12 w-12" :ui="{ rounded: 'rounded-full' }" />
<div class="space-y-2">
<USkeleton class="h-4 w-[250px]" />
<USkeleton class="h-4 w-[200px]" />
</div>
</div>
</div>

<!-- Controlls -->
<div class="absolute bottom-4 right-4 flex gap-4 print:hidden z-10">
<ScaleController
v-if="scale != null"
class="bg-white rounded shadow-2xl"
:scale="scale"
@reset-zoom="resetZoom"
@zoom-in="zoomIn"
@zoom-out="zoomOut"
/>
<FitController class="bg-white rounded shadow-2xl" />
</div>
</div>
</template>

<style scoped>
.dots-bg {
background-size: 40px 40px;
background-image: radial-gradient(
circle,
rgba(255, 255, 255, 20%) 2px,
rgba(255, 255, 255, 0) 1px
);
}
</style>
9 changes: 7 additions & 2 deletions web/components/PartListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const height = usePx(() => props.placement.lengthM);
const left = usePx(() => props.placement.leftM);
const bottom = usePx(() => props.placement.bottomM);
const getPx = useGetPx();
const fontSize = usePx(() =>
Math.min(
props.placement.widthM / 2,
Expand Down Expand Up @@ -44,6 +43,12 @@ const showPartNumbers = useShowPartNumbers();
{{ placement.partNumber }}
</p>
</UPlaceholder>
<PartDetailsTooltip v-if="isHovered" :part="placement" />
<Teleport to="body">
<PartDetailsTooltip
v-if="isHovered"
:part="placement"
class="pointer-events-none"
/>
</Teleport>
</div>
</template>
20 changes: 14 additions & 6 deletions web/components/ScaleController.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<script lang="ts" setup>
const scale = useScale();
const { zoomIn, zoomOut, resetZoom } = useScaleControls();
const props = defineProps<{
scale: number;
}>();
const percent = computed(() => `${Math.round(scale.value * 100)}%`);
const emit = defineEmits<{
zoomOut: [];
zoomIn: [];
resetZoom: [];
}>();
const percent = computed(() => `${Math.round(props.scale * 100)}%`);
</script>

<template>
Expand All @@ -13,23 +20,24 @@ const percent = computed(() => `${Math.round(scale.value * 100)}%`);
size="lg"
color="black"
icon="i-heroicons-minus"
@click="zoomOut"
@click="emit('zoomOut')"
/>
<UButton
:title="`${percent} - Click to reset to 100%`"
class="w-20 justify-center"
size="lg"
color="black"
@click="resetZoom"
@click="emit('resetZoom')"
>
{{ percent }}
</UButton>
<UButton
title="Zoom in"
square
size="lg"
color="black"
icon="i-heroicons-plus"
@click="zoomIn"
@click="emit('zoomIn')"
/>
</div>
</template>
3 changes: 1 addition & 2 deletions web/composables/useGetPx.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export default function () {
const scale = useScale();
return (value: number) => `${value * 500 * scale.value}px`;
return (value: number) => `${value * 500}px`;
}
51 changes: 51 additions & 0 deletions web/composables/usePanZoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { PanZoom } from 'panzoom';
import panzoom from 'panzoom';

export default function (container: Ref<HTMLElement | undefined>) {
let instance = ref<PanZoom>();
const scale = ref<number>();

whenever(container, (container) => {
instance.value = panzoom(container, {
autocenter: true,
minZoom: 0.2,
maxZoom: 10,
});
scale.value = instance.value.getTransform().scale;
instance.value.on('zoom', () => {
scale.value = instance.value?.getTransform().scale;
});
});
whenever(
() => !container.value,
() => {
instance.value?.dispose();
},
);
onUnmounted(() => {
instance.value?.dispose();
});

const setZoom = (cb: (scale: number) => number, x?: number, y?: number) => {
if (instance.value == null) return;
const current = instance.value.getTransform();
const currentScale = current.scale;
const newScale = cb(current.scale);
instance.value?.smoothZoom(
x ?? current.x,
y ?? current.y,
newScale / currentScale,
);
};
const zoomBy = (increment: number) => setZoom((scale) => scale + increment);

return {
scale,
zoomIn: () => zoomBy(0.1),
zoomOut: () => zoomBy(-0.1),
resetZoom: () => {
setZoom(() => 1);
instance.value?.smoothMoveTo(0, 0);
},
};
}
1 change: 0 additions & 1 deletion web/composables/useScale.ts

This file was deleted.

8 changes: 0 additions & 8 deletions web/composables/useScaleControls.ts

This file was deleted.

26 changes: 13 additions & 13 deletions web/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ const isExpanded = useIsExpanded();
<ClientOnly>
<MainSidebar
v-if="!isExpanded"
class="bg-gray-50 dark:bg-gray-800 shrink-0 print:hidden min-w-[28rem]"
class="bg-gray-50 dark:bg-gray-800 shrink-0 print:hidden min-w-[28rem] relative z-10"
/>
</ClientOnly>

<ClientOnly>
<div class="flex-1 relative">
<!-- Cutlist Preview -->
<div class="absolute inset-0 overflow-auto">
<CutlistPreview class="min-h-full min-w-full dots-bg w-max" />
</div>

<!-- Controlls -->
<div class="absolute bottom-4 right-4 flex gap-4 print:hidden">
<ScaleController class="bg-white rounded shadow-2xl" />
<FitController class="bg-white rounded shadow-2xl" />
</div>
</div>
<CutlistPreview class="flex-1 relative z-0 dots-bg" />
</ClientOnly>
</div>
</template>

<style scoped>
.dots-bg {
background-size: 40px 40px;
background-image: radial-gradient(
circle,
rgba(255, 255, 255, 20%) 2px,
rgba(255, 255, 255, 0) 1px
);
}
</style>

0 comments on commit a0ab3d9

Please sign in to comment.