Skip to content

Commit

Permalink
fix(NcBreadcrumbs): adjust component for vue filepicker
Browse files Browse the repository at this point in the history
Signed-off-by: Raimund Schlüßler <raimund.schluessler@mailbox.org>
  • Loading branch information
raimund-schluessler committed Aug 23, 2023
1 parent 30c4b20 commit 97c16bb
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 74 deletions.
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -79,6 +79,7 @@
"unist-util-visit": "^4.1.2",
"vue": "^2.7.14",
"vue-color": "^2.8.1",
"vue-frag": "^1.4.3",
"vue-material-design-icons": "^5.1.2",
"vue-multiselect": "^2.1.6",
"vue2-datepicker": "^3.11.0"
Expand Down
178 changes: 104 additions & 74 deletions src/components/NcBreadcrumbs/NcBreadcrumbs.vue
Expand Up @@ -138,6 +138,7 @@ export default {

<script>
import NcActions from '../NcActions/index.js'
import NcActionButton from '../NcActionButton/index.js'
import NcActionRouter from '../NcActionRouter/index.js'
import NcActionLink from '../NcActionLink/index.js'
import NcBreadcrumb from '../NcBreadcrumb/index.js'
Expand All @@ -149,13 +150,15 @@ import IconFolder from 'vue-material-design-icons/Folder.vue'
import debounce from 'debounce'
import Vue from 'vue'
import { Fragment } from 'vue-frag'
const crumbClass = 'vue-crumb'
export default {
name: 'NcBreadcrumbs',
components: {
NcActions,
NcActionButton,
NcActionRouter,
NcActionLink,
NcBreadcrumb,
Expand All @@ -173,10 +176,6 @@ export default {
emits: ['dropped'],
data() {
return {
/**
* The breadcrumbs which should be shown in a dropdown Actions menu.
*/
hiddenCrumbs: [],
/**
* Array to track the hidden breadcrumbs by their index.
* Comparing two crumbs somehow does not work, so we use the indices.
Expand All @@ -196,6 +195,7 @@ export default {
// Is the menu open or not
open: false,
},
breadcrumbsRefs: {},
}
},
beforeMount() {
Expand Down Expand Up @@ -226,22 +226,15 @@ export default {
/**
* Check that crumbs to hide are hidden
*/
this.delayedHideCrumbs()
this.$nextTick(() => {
this.hideCrumbs()
})
},
beforeDestroy() {
window.removeEventListener('resize', this.handleWindowResize)
unsubscribe('navigation-toggled', this.delayedResize)
},
methods: {
/**
* Check that all crumbs to hide are really hidden
*/
delayedHideCrumbs() {
this.$nextTick(() => {
const crumbs = this.$slots.default || []
this.hideCrumbs(crumbs)
})
},
/**
* Close the actions menu
*
Expand All @@ -257,50 +250,49 @@ export default {
/**
* Call the resize function after a delay
*/
delayedResize() {
this.$nextTick(() => {
this.handleWindowResize()
})
async delayedResize() {
await this.$nextTick()
this.handleWindowResize()
},
/**
* Check the width of the breadcrumb and hide breadcrumbs
* if we overflow otherwise.
*/
handleWindowResize() {
// All breadcrumb components passed into the default slot
const breadcrumbs = this.$slots.default || []
// If there is no container yet, we cannot determine its size
if (this.$refs.container) {
const nrCrumbs = breadcrumbs.length
const hiddenIndices = []
const availableWidth = this.$refs.container.offsetWidth
let totalWidth = this.getTotalWidth(breadcrumbs)
// If we have breadcumbs actions, we have to take their width into account too.
if (this.$refs.breadcrumb__actions) {
totalWidth += this.$refs.breadcrumb__actions.offsetWidth
}
let overflow = totalWidth - availableWidth
// If we overflow, we have to take the action-item width into account as well.
overflow += (overflow > 0) ? 64 : 0
let i = 0
// We start hiding the breadcrumb in the center
const startIndex = Math.floor(nrCrumbs / 2)
// Don't hide the first and last breadcrumb
while (overflow > 0 && i < nrCrumbs - 2) {
// We hide elements alternating to the left and right
const currentIndex = startIndex + ((i % 2) ? i + 1 : i) / 2 * Math.pow(-1, i + (nrCrumbs % 2))
// Calculate the remaining overflow width after hiding this breadcrumb
overflow -= this.getWidth(breadcrumbs[currentIndex].elm)
hiddenIndices.push(currentIndex)
i++
}
// We only update the hidden crumbs if they have changed,
// otherwise we will run into an infinite update loop.
if (!this.arraysEqual(this.hiddenIndices, hiddenIndices.sort((a, b) => a - b))) {
// Get all breadcrumbs based on the hidden indices
this.hiddenCrumbs = hiddenIndices.map((index) => { return breadcrumbs[index] })
this.hiddenIndices = hiddenIndices
}
if (!this.$refs.container) {
return
}
// All breadcrumb components passed into the default slot
const breadcrumbs = Object.values(this.breadcrumbsRefs)
const nrCrumbs = breadcrumbs.length
const hiddenIndices = []
const availableWidth = this.$refs.container.offsetWidth
let totalWidth = this.getTotalWidth(breadcrumbs)
// If we have breadcumbs actions, we have to take their width into account too.
if (this.$refs.breadcrumb__actions) {
totalWidth += this.$refs.breadcrumb__actions.offsetWidth
}
let overflow = totalWidth - availableWidth
// If we overflow, we have to take the action-item width into account as well.
overflow += (overflow > 0) ? 64 : 0
let i = 0
// We start hiding the breadcrumb in the center
const startIndex = Math.floor(nrCrumbs / 2)
// Don't hide the first and last breadcrumb
while (overflow > 0 && i < nrCrumbs - 2) {
// We hide elements alternating to the left and right
const currentIndex = startIndex + ((i % 2) ? i + 1 : i) / 2 * Math.pow(-1, i + (nrCrumbs % 2))
// Calculate the remaining overflow width after hiding this breadcrumb
overflow -= this.getWidth(breadcrumbs[currentIndex]?.elm)
hiddenIndices.push(currentIndex)
i++
}
// We only update the hidden crumbs if they have changed,
// otherwise we will run into an infinite update loop.
if (!this.arraysEqual(this.hiddenIndices, hiddenIndices.sort((a, b) => a - b))) {
this.hiddenIndices = hiddenIndices
}
},
/**
Expand Down Expand Up @@ -330,7 +322,7 @@ export default {
* @return {number} The total width
*/
getTotalWidth(breadcrumbs) {
return breadcrumbs.reduce((width, crumb, index) => width + this.getWidth(crumb.elm), 0)
return breadcrumbs.reduce((width, crumb, index) => width + this.getWidth(crumb?.elm), 0)
},
/**
* Calculates the width of the provided element
Expand All @@ -339,7 +331,7 @@ export default {
* @return {number} The width
*/
getWidth(el) {
if (!el.classList) return 0
if (!el?.classList) return 0
const hide = el.classList.contains(`${crumbClass}--hidden`)
el.style.minWidth = 'auto'
el.classList.remove(`${crumbClass}--hidden`)
Expand Down Expand Up @@ -464,21 +456,23 @@ export default {
/**
* Check for each crumb if we have to hide it and
* add it to the array of all crumbs.
*
* @param {Array} crumbs The array of the crumbs to hide
* @param {number} offset The offset of the indices of the provided crumbs array
*/
hideCrumbs(crumbs, offset = 0) {
hideCrumbs() {
const crumbs = Object.values(this.breadcrumbsRefs)
crumbs.forEach((crumb, i) => {
if (crumb?.elm?.classList) {
if (this.hiddenIndices.includes(i + offset)) {
if (this.hiddenIndices.includes(i)) {
crumb.elm.classList.add(`${crumbClass}--hidden`)
} else {
crumb.elm.classList.remove(`${crumbClass}--hidden`)
}
}
})
},
isBreadcrumb(vnode) {
return (vnode?.componentOptions?.tag || vnode?.tag || '').includes('NcBreadcrumb')
},
},
/**
* The render function to display the component
Expand All @@ -488,7 +482,22 @@ export default {
*/
render(h) {
// Get the breadcrumbs
const breadcrumbs = this.$slots.default || []
const breadcrumbs = []
// We have to iterate over all slot elements
this.$slots.default.forEach(vnode => {
if (this.isBreadcrumb(vnode)) {
breadcrumbs.push(vnode)
return
}
// If we encounter a Fragment, we have to check its children too
if (vnode?.type === Fragment) {
vnode?.children?.forEach?.(child => {
if (this.isBreadcrumb(child)) {
breadcrumbs.push(child)
}
})
}
})
// Check that we have at least one breadcrumb
if (breadcrumbs.length === 0) {
Expand All @@ -498,23 +507,35 @@ export default {
// Add the root icon to the first breadcrumb
// eslint-disable-next-line import/no-named-as-default-member
Vue.set(breadcrumbs[0].componentOptions.propsData, 'icon', this.rootIcon)
// eslint-disable-next-line import/no-named-as-default-member
Vue.set(breadcrumbs[0].componentOptions.propsData, 'ref', 'breadcrumbs')
/**
* Use a proxy object to store breadcrumbs refs
* and don't write to this.breadcrumbsRefs directly
* to not trigger a myriad of re-renders.
*/
const breadcrumbsRefs = {}
// Add the breadcrumbs to the array of the created VNodes, check if hiding them is necessary.
breadcrumbs.forEach((crumb, index) => {
// eslint-disable-next-line import/no-named-as-default-member
Vue.set(crumb, 'ref', `crumb-${index}`)
breadcrumbsRefs[index] = crumb
})
// The array of all created VNodes
let crumbs = []
if (!this.hiddenCrumbs.length) {
if (!this.hiddenIndices.length) {
// We don't hide any breadcrumbs.
crumbs = breadcrumbs
// But we need to check if some are already hidden, and show them.
this.hideCrumbs(crumbs)
} else {
/**
* We show the first half of the breadcrumbs before the Actions dropdown menu
* which shows the hidden breadcrumbs.
*/
// Add the breadcrumbs to the array of the created VNodes, check if hiding them is necessary.
crumbs = breadcrumbs.slice(0, Math.round(breadcrumbs.length / 2))
this.hideCrumbs(crumbs)
// The Actions menu
// Use a breadcrumb component for the hidden breadcrumbs
Expand Down Expand Up @@ -546,7 +567,8 @@ export default {
},
},
// Add all hidden breadcrumbs as ActionRouter or ActionLink
}, this.hiddenCrumbs.map(crumb => {
}, this.hiddenIndices.map(index => {
const crumb = breadcrumbs[index]
// Get the parameters from the breadcrumb component props
const to = crumb.componentOptions.propsData.to
const href = crumb.componentOptions.propsData.href
Expand All @@ -555,9 +577,13 @@ export default {
// TODO: Remove this fallback once nameTitleFallback is removed from NcBreadcrumb
const name = crumb.componentOptions.propsData.name || title
// Decide whether to show the breadcrumbs as ActionRouter or ActionLink
let element = 'NcActionLink'
let path = href
// Decide whether to show the breadcrumbs as ActionButton, ActionRouter or ActionLink
let element = 'NcActionButton'
let path = ''
if (href) {
element = 'NcActionLink'
path = href
}
if (to) {
element = 'NcActionRouter'
path = to
Expand All @@ -571,15 +597,18 @@ export default {
return h(element, {
class: crumbClass,
props: {
href,
href: href || null,
title,
name: '', // TODO: Remove this once nameTitleFallback is removed from actionText.js mixin
to,
to: to || null,
},
// Prevent the breadcrumbs from being draggable
attrs: {
draggable: false,
},
on: {
...crumb.componentOptions.listeners,
},
// Add the drag and drop handlers
nativeOn: {
dragstart: this.dragStart,
Expand All @@ -597,17 +626,17 @@ export default {
// The second half of the breadcrumbs
const crumbs2 = breadcrumbs.slice(Math.round(breadcrumbs.length / 2))
crumbs = crumbs.concat(crumbs2)
// One crumb is the Actions dropdown, so subtract one.
this.hideCrumbs(crumbs2, crumbs.length - 1)
}
const wrapper = [h('nav', {}, [h('ul', { class: 'breadcrumb__crumbs' }, crumbs)])]
const wrapper = [h('nav', {}, [h('ul', { class: 'breadcrumb__crumbs' }, [crumbs])])]
// Append the actions slot if it is populated
if (this.$slots.actions) {
wrapper.push(h('div', { class: 'breadcrumb__actions', ref: 'breadcrumb__actions' }, this.$slots.actions))
}
return h('div', { class: ['breadcrumb', { 'breadcrumb--collapsed': (this.hiddenCrumbs.length === breadcrumbs.length - 2) }], ref: 'container' }, wrapper)
this.breadcrumbsRefs = breadcrumbsRefs
return h('div', { class: ['breadcrumb', { 'breadcrumb--collapsed': (this.hiddenIndices.length === breadcrumbs.length - 2) }], ref: 'container' }, wrapper)
},
}
</script>
Expand All @@ -617,8 +646,9 @@ export default {
width: 100%;
flex-grow: 1;
display: inline-flex;
align-items: center;
&--collapsed .vue-crumb:last-child {
&--collapsed :deep(.vue-crumb:last-child) {
min-width: 100px;
flex-shrink: 1;
}
Expand Down

0 comments on commit 97c16bb

Please sign in to comment.