Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Drag into sidebar always docks into an empty zone #499

Merged
merged 4 commits into from
Nov 20, 2024
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
12 changes: 3 additions & 9 deletions src/components/Tab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import interact from 'interactjs'
import type {InteractEvent} from '@interactjs/types'
import {
maybeClearDropzoneContainer,
positionAndSizeTab,
positionAndSizeAllTabs,
selectTab,
Expand Down Expand Up @@ -258,22 +259,15 @@
)
const dropzoneContainer = dropzone?.parentElement?.parentElement
if (
tab instanceof HTMLElement
&& dropzone instanceof HTMLElement
dropzone instanceof HTMLElement
&& dropzoneContainer instanceof HTMLElement
&& tab.classList.contains('docked')
) {
// update classlists when undocking
dropzone.classList.add('empty')
tab.classList.remove('docked')
tab.setAttribute('docked', 'none')
// if both dropzones are empty,
// make the dropzone container empty aswell
if (
dropzoneContainer.querySelectorAll('.empty').length == 2
) {
dropzoneContainer.classList.add('empty')
}
maybeClearDropzoneContainer(dropzoneContainer)
}
return
}
Expand Down
142 changes: 103 additions & 39 deletions src/views/Scope.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ visualizers you can select.

/**
* Positions a tab to be inside a dropzone
* Resizes the tab to be the same size as the dropzone
* If the other dropzone in its container is empty, resizes the zone
* to be the same size as the tab; otherwise, resizes the tab to be the
* same size as the dropzone.
*
* @param tab The HTML container that the draggable tab lives in
* @param dropzone The dropzone the tab is docked to
*/
Expand All @@ -189,9 +192,38 @@ visualizers you can select.
): void {
if (isMobile()) return

const dropzoneContainer = dropzone.parentElement?.parentElement
const dropzoneWrapper = dropzone.parentElement
if (!(dropzoneWrapper instanceof HTMLElement)) return
const dropzoneContainer = dropzoneWrapper.parentElement
if (!(dropzoneContainer instanceof HTMLElement)) return
const containerRect = dropzoneContainer.getBoundingClientRect()
const tabRect = tab.getBoundingClientRect()

let resizeDropzone = !!dropzoneContainer.querySelector('.empty')
let toHeight = 0
if (resizeDropzone) {
// Find the resizable dropzone wrapper
const resizeHandle =
dropzoneContainer.querySelector('.dropzone-resize')
if (!(resizeHandle instanceof HTMLElement)) return
const resizableWrapper = resizeHandle.parentElement
if (!(resizableWrapper instanceof HTMLElement)) return
if (resizableWrapper.classList.contains('resized')) {
resizeDropzone = false
} else {
if (resizableWrapper === dropzoneWrapper) {
toHeight = tabRect.height + 16
} else {
toHeight = containerRect.height - tabRect.height
}
resizableWrapper.style.height = toHeight + 'px'
resizableWrapper.classList.add('resized')
}
}
const dropzoneRect = dropzone.getBoundingClientRect()
if (!resizeDropzone) tab.style.height = dropzoneRect.height + 'px'

// Now that everything is the correct size, reposition the tab:
const {x, y} = translateCoords(dropzoneRect.x, dropzoneRect.y)

tab.style.top = y + 'px'
Expand All @@ -203,11 +235,10 @@ visualizers you can select.
tab.style.removeProperty('right')
tab.style.left = x + 'px'
}
tab.style.height = dropzoneRect.height + 'px'

// update the classlist with "minimized"
// if the height is less or equal than 110
if (dropzoneRect.height <= 110) {
if (parseInt(getComputedStyle(tab).height) <= 110) {
tab.classList.add('minimized')
} else {
tab.classList.remove('minimized')
Expand All @@ -216,12 +247,7 @@ visualizers you can select.
tab.setAttribute('data-x', x.toString())
tab.setAttribute('data-y', y.toString())

if (
tab instanceof HTMLElement
&& dropzoneContainer instanceof HTMLElement
&& dropzone instanceof HTMLElement
&& dropzone.classList.contains('empty')
) {
if (dropzone.classList.contains('empty')) {
dropzone.classList.remove('empty')
dropzone.classList.remove('drop-hover')
dropzoneContainer.classList.remove('empty')
Expand Down Expand Up @@ -258,12 +284,26 @@ visualizers you can select.
return {x, y}
}

/**
* Resets the state of a dropzone container if it empties out
*/
export function maybeClearDropzoneContainer(container: Element) {
if (container.querySelectorAll('.empty').length == 2) {
container.classList.add('empty')
const resizableWrapper = container.firstChild
if (!(resizableWrapper instanceof HTMLElement)) return
resizableWrapper.classList.remove('resized')
resizableWrapper.style.removeProperty('height')
}
}

/**
* Places every docked tab back in its position and size.
* Doesn't affect non-docked tabs.
* Used when the window is resized.
*/
export function positionAndSizeAllTabs(): void {
// First reset the "empty" classes and recompute them, just in case:
document
.querySelectorAll('.dropzone')
.forEach((dropzone: Element) => {
Expand All @@ -279,19 +319,25 @@ visualizers you can select.
)
if (!(dropzone instanceof HTMLElement)) return
dropzone.classList.remove('empty')
positionAndSizeTab(tab, dropzone)
})

document
.querySelectorAll('.dropzone-container')
.forEach((container: Element) => {
if (container.querySelectorAll('.empty').length == 2) {
container.classList.add('empty')
} else {
container.classList.remove('empty')
}
})
.forEach(dc => maybeClearDropzoneContainer(dc))

// Now actually position and resize all of the tabs
document.querySelectorAll('.tab').forEach((tab: Element) => {
if (!(tab instanceof HTMLElement)) return
if (tab.getAttribute('docked') === 'none') return

const dropzone = document.querySelector(
'#' + tab.getAttribute('docked') + '-dropzone'
)
if (!(dropzone instanceof HTMLElement)) return
positionAndSizeTab(tab, dropzone)
})
}

// selects a tab
export function selectTab(tab: HTMLElement): void {
deselectTab()
Expand All @@ -302,6 +348,7 @@ visualizers you can select.
drag.style.backgroundColor = 'var(--ns-color-primary)'
tab.style.zIndex = '100'
}

// deselects all tabs
export function deselectTab(): void {
const tabs = document.querySelectorAll('.tab')
Expand Down Expand Up @@ -434,19 +481,21 @@ visualizers you can select.
overlap: 0.5,
// activates when a tab is dragged over a dropzone
ondragenter: function (event: InteractEvent) {
event.target.classList.add('drop-hover')
event.target.parentElement?.parentElement?.classList.add(
'drop-hover'
)
},

// activates when a tab is dragged out of a dropzone
ondragleave: function (event: InteractEvent) {
event.target.classList.remove('drop-hover')

const dropzone = event.target
const dropzoneContainer = dropzone.parentElement?.parentElement
if (!(dropzoneContainer instanceof HTMLElement)) return
dropzoneContainer.classList.remove('drop-hover')

const tab = event.relatedTarget?.parentElement
if (
tab instanceof HTMLElement
&& dropzoneContainer instanceof HTMLElement
&& tab.classList.contains('docked')
) {
// Both individual dropzones and their containers have an
Expand All @@ -457,28 +506,45 @@ visualizers you can select.
dropzone.classList.add('empty')
tab.classList.remove('docked')
tab.setAttribute('docked', 'none')

if (
dropzoneContainer.querySelectorAll('.empty').length == 2
) {
dropzoneContainer.classList.add('empty')
}
maybeClearDropzoneContainer(dropzoneContainer)
}
},

// activates when tab is dropped in dropzone
ondrop: function (event) {
const tab = event.relatedTarget.parentElement
const dropzone = event.target
let dropzone = event.target
const dropzoneContainer = dropzone.parentElement.parentElement

if (
tab instanceof HTMLElement
&& dropzoneContainer instanceof HTMLElement
&& dropzone.classList.contains('empty')
) {
let occupied = !dropzone.classList.contains('empty')
if (
tab.classList.contains('docked')
&& tab.getAttribute('docked')
=== dropzone.getAttribute('dropzone')
)
occupied = false // ok to just go back into zone you're in
if (occupied) {
// Drop into any empty dropzone in same container
const oldDropzone = dropzone
for (const wrapper of dropzoneContainer.children) {
const newDropzone = wrapper.children[0]
if (
newDropzone !== oldDropzone
&& newDropzone.classList.contains('empty')
) {
dropzone = newDropzone
break
}
}
if (dropzone === oldDropzone) return // no empty zone here
}
// We have found an empty drop zone
dropzone.classList.remove('empty')
dropzone.classList.remove('drop-hover')
dropzoneContainer.classList.remove('drop-hover')
dropzoneContainer.classList.remove('empty')
tab.classList.add('docked')
tab.setAttribute('docked', dropzone.getAttribute('dropzone'))
Expand Down Expand Up @@ -511,9 +577,7 @@ visualizers you can select.

move(event: InteractEvent) {
const dropzoneWrapper = event.target
const dropzoneCont =
dropzoneWrapper.parentElement?.parentElement

const dropzoneCont = dropzoneWrapper.parentElement
if (
dropzoneWrapper instanceof HTMLElement
&& dropzoneCont instanceof HTMLElement
Expand Down Expand Up @@ -689,15 +753,15 @@ visualizers you can select.

.dropzone {
flex-grow: 1;

&.drop-hover {
background-color: var(--ns-color-primary);
filter: brightness(120%);
}
}
}
}

.dropzone-container.drop-hover .dropzone {
background-color: var(--ns-color-primary);
filter: brightness(120%);
}

.tab {
width: 300px;
position: absolute;
Expand Down