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

[stable7] fix(NcBreadcrumbs): adjust component for vue filepicker #4452

Merged
merged 1 commit into from Aug 23, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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