Skip to content
Merged
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
117 changes: 92 additions & 25 deletions app/src/renderer/components/panzoom/Panzoom.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,65 @@
'dev-visual-debugger': showCanvasDebugger,
}"
>
<!-- For transform-origin: 50% 50% to work, this next element HAS to be display:block -->
<div
ref="innerPanArea"
:class="{
'dev-visual-debugger': showCanvasDebugger,
}"
style="
display: block;
position: relative;
height: 100%;
width: 100%;
overflow: visible;
"
>
<slot />
<!-- Now we have the real panzoom content inside: -->
<div
class="panzoom-inner"
:class="{
'dev-visual-debugger': showCanvasDebugger,
}"
>
<slot />
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, getCurrentInstance, onMounted, computed, watch } from 'vue'
import {
ref,
getCurrentInstance,
onMounted,
computed,
watch,
nextTick,
} from 'vue'
import Panzoom, { PanzoomObject } from '@panzoom/panzoom'
import { ipcRenderer } from 'electron'
import isElectron from 'is-electron'
import PanzoomControls from './PanzoomControls.vue'
import { useInteractionStore } from '~/store/interactions'
import useEventHandler from '../Screens/useEventHandler'
import { useDevStore } from '~/store/dev'
import { isEqual } from 'lodash'
import { start } from 'repl'
import { initialPanZoom } from './panzoomFns'
// import { useEventListener } from '@vueuse/core'

const interactions = useInteractionStore()
const devStore = useDevStore()
const { state: userEventsState } = useEventHandler()

// Store
// const showCanvasDebugger = false // TODO: Migrate from Vuex -> Pinia
const showCanvasDebugger = devStore.showCanvasDebugger
const showCanvasDebugger = computed(() => devStore.showCanvasDebugger)
const panzoomEnabled = computed(() => interactions.panzoomEnabled)
const isInteracting = computed(() => interactions.isInteracting)

// Template refs
const innerPanArea = ref()
const outerPanArea = ref() // The parent DOM Node
const panzoomContent = ref() // Dynamic content within Panzoom
const panzoomInstance = ref<PanzoomObject | undefined>()

// Watch for changes and change Panzoom accordingly
Expand All @@ -61,19 +84,45 @@ watch(panzoomEnabled, (newVal) => {
}
})

onMounted(async () => {
// Dynamic height and width for parent based on child
const dynamicDims = computed(() => {
const { width, height } = panzoomContent.value?.getBoundingClientRect() || {
width: 0,
height: 0,
}

console.log('dynamicDims', width, height)

return {
width: width + 'px',
height: height + 'px',
}
})
// TODO: This should be refactored after Vue 3 update
const { $root } = getCurrentInstance()?.proxy
if (!$root) console.warn('No Panzoom created')
if (!$root) console.warn('No $root')

onMounted(async () => {
await nextTick()

// const startZoom = initialZoom()
const { x: startX } = initialPanZoom()
const { y: startY } = initialPanZoom()
const { zoom: startZoom } = initialPanZoom()

// Init Panzoom globally
// We use the 'canvas' option to enable interactions on the parent DOM Node as well
// Docs: https://github.com/timmywil/panzoom#canvas
$root.$panzoom = Panzoom(innerPanArea.value, {
canvas: true, // Allows parent to control child
cursor: 'grab',
startScale: 1,
startX: 0,
// origin = transform-origin is the origin from which transforms are applied
// Default: 50% 50% https://github.com/timmywil/panzoom#origin
origin: '50% 50%',
startX: startX, // x
startY: startY, // y
// contain: false,
startScale: startZoom,
handleStartEvent: (event: PointerEvent) => {
// WARNING: Don't use preventDefault, as it will block other events
// event.preventDefault()
Expand All @@ -99,14 +148,22 @@ onMounted(async () => {
// Reference inside of this component
panzoomInstance.value = $root.$panzoom

// Center Panzoom
// this.panzoomInstance.pan(
// origX + (current.clientX - startClientX) / scale,
// origY + (current.clientY - startClientY) / scale,
// {
// animate: false,
// TODO: Remove later; useful for debugging
// let zoom, pan
// setInterval(() => {
// const newZoom = panzoomInstance.value?.getScale()
// const newPan = panzoomInstance.value?.getPan()

// const isNewZoom = zoom !== newZoom
// const isNewPan = isEqual(pan, newPan) === false

// if (isNewZoom) zoom = newZoom
// if (isNewPan) pan = newPan

// if (isNewPan || isNewZoom) {
// console.log('Panzoom', { zoom, ...pan }, isNewZoom, isNewPan)
// }
// )
// }, 1000)

// Enable event listeners
enableEventListeners()
Expand Down Expand Up @@ -263,18 +320,20 @@ function fitToScreen() {

<style lang="scss" scoped>
.panzoom-container {
// position: absolute;
// overflow: hidden;
// outline: none;
position: relative !important;
height: 100%;
width: 100%;
position: relative;
// position: absolute;
top: 0;
left: 0;
overflow: hidden;
}

.panzoom-inner {
position: relative; // Important
display: inline-block;
overflow: visible;
}

#parent {
height: auto;
width: auto;
Expand Down Expand Up @@ -318,14 +377,22 @@ function fitToScreen() {
.dev-visual-debugger {
// Vertical line
&:before {
position: absolute;
width: 100%;
z-index: 100;
height: 100%;
left: 50%;
border-left: 1px solid green;
border-left: 5px solid green;
}

// Horizonal line
&:after {
position: absolute;
width: 100%;
z-index: 100;
height: 100%;
top: 50%;
border-top: 1px solid green;
border-top: 5px solid green;
}
}
}
Expand Down
62 changes: 62 additions & 0 deletions app/src/renderer/components/panzoom/panzoomFns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// The CSS classes
// TODO: Add tests to make sure these don't break
const panzoomContainer = '.panzoom-container'
const panzoomChild = '.panzoom-inner'

/**
* Returns the x, y, zoom for the initial app startup
*/
export function initialPanZoom() {
const panzoomEl = document.querySelector<HTMLElement>(panzoomContainer)
if (!panzoomEl) {
console.error('No container')
return {}
}
const childElement = panzoomEl?.querySelector(panzoomChild) as HTMLElement

const panzoomRect = panzoomEl.getBoundingClientRect()
const childRect = childElement.getBoundingClientRect()

// Calculate the zoom to fit all artboards
const zoom = calculateZoomToFitAll()

// Pan
const panX = (panzoomRect.width - childRect.width) / 2 - childRect.left
const panY = (panzoomRect.height - childRect.height) / 2 - childRect.top

// center child element within viewport
return {
x: panX,
y: panY,
zoom: zoom,
}
}
/**
* Returns a zoom factor which would fit all the current artboards within the viewport
*/
export function calculateZoomToFitAll(): number {
const panzoomEl = document.querySelector<HTMLElement>(panzoomContainer)
if (!panzoomEl) {
console.error('No container')
return 1
}
const childElement = panzoomEl?.querySelector(panzoomChild) as HTMLElement

const panzoomViewportRect = {
width: panzoomEl.offsetWidth,
height: panzoomEl.offsetHeight,
}
const childRect = childElement.getBoundingClientRect()
const childWidth = childRect.width
const childHeight = childRect.height

// Calculate the zoom scale
const zoomPadding = 0.9 // add extra space on sides of the content
const zoomX = (panzoomViewportRect.width / childWidth) * zoomPadding
const zoomY = (panzoomViewportRect.height / childHeight) * zoomPadding
const zoom = Math.min(zoomX, zoomY)

// Return
return zoom
}