after 9000 hours it finally works

This commit is contained in:
Henry Jameson 2025-11-20 12:12:14 +02:00
commit e6f025bf6e
7 changed files with 131 additions and 49 deletions

View file

@ -88,6 +88,12 @@ const SettingsModalContent = {
// Clear the state of target tab, so that next time settings is opened
// it doesn't force it.
useInterfaceStore().clearSettingsModalTargetTab()
},
nestedTooBig () {
this.$refs.tabSwitcher.showNav()
},
nestedTooSmall () {
this.$refs.tabSwitcher.hideNav()
}
},
mounted () {

View file

@ -2,24 +2,33 @@
<vertical-tab-switcher
ref="tabSwitcher"
class="settings_tab-switcher"
:side-tab-bar="true"
:scrollable-tabs="true"
:body-scroll-lock="bodyLock"
>
<div
:full-width="true"
:label="$t('settings.general')"
icon="wrench"
data-tab-name="general"
>
<GeneralTab />
<GeneralTab
class="inner-tab -middle"
@too-small="() => nestedTooSmall()"
@too-big="() => nestedTooBig()"
/>
</div>
<div
:full-width="true"
:label="$t('settings.appearance')"
icon="window-restore"
data-tab-name="appearance"
:delay-render="true"
>
<AppearanceTab />
<AppearanceTab
class="inner-tab -middle"
@too-small="() => nestedTooSmall()"
@too-big="() => nestedTooBig()"
/>
</div>
<div
v-if="expertLevel > 0"

View file

@ -5,6 +5,8 @@
ref="tabSwitcher"
:side-tab-bar="true"
:scrollable-tabs="true"
@too-small="() => console.log('small') || $emit('tooSmall')"
@too-big="() => $emit('tooBig')"
>
<div
:label="$t('settings.interface')"

View file

@ -3,6 +3,8 @@
:label="$t('settings.general')"
ref="tabSwitcher"
class="settings_tab-switcher"
@too-small="() => $emit('tooSmall')"
@too-big="() => $emit('tooBig')"
>
<div
:label="$t('settings.behavior')"

View file

@ -158,7 +158,10 @@ export default {
})
return (
<div class="tab-switcher top-tabs">
<div
class="tab-switcher top-tabs"
ref="root"
>
<div
class="tabs"
role="tablist"
@ -170,6 +173,7 @@ export default {
role="tabpanel"
class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}
v-body-scroll-lock={this.bodyScrollLock}
ref="content"
>
{contents}
</div>

View file

@ -34,10 +34,12 @@ export default {
default: false
}
},
emits: ['tooBig', 'tooSmall'],
data () {
return {
active: findFirstUsable(this.slots()),
resizeHandler: null
resizeHandler: null,
navMode: false
}
},
computed: {
@ -59,6 +61,16 @@ export default {
mobileLayout: store => store.layoutType === 'mobile'
}),
},
created () {
this.resizeHandler = throttle(this.onResize, 200)
window.addEventListener('resize', this.resizeHandler)
},
mounted () {
this.resizeHandler()
},
unmounted () {
window.removeEventListener('resize', this.resizeHandler)
},
beforeUpdate () {
const currentSlot = this.slots()[this.active]
if (!currentSlot.props) {
@ -70,7 +82,20 @@ export default {
return (e) => {
e.preventDefault()
this.setTab(index)
console.log(index)
}
},
onResize (index) {
const tabContent = this.$refs.contents?.querySelector('.tab-content-wrapper.-active .tab-content')
const tabContentWidth = tabContent.clientWidth
const rootWidth = this.$refs.root?.clientWidth
const navWidth = this.$refs.nav?.clientWidth
const contentsWidth = this.$refs.contents?.clientWidth
if (contentsWidth < tabContentWidth) {
this.$emit('tooSmall')
} else if (contentsWidth - navWidth >= tabContentWidth){
this.$emit('tooBig')
}
},
// DO NOT put it to computed, it doesn't work (caching?)
@ -85,7 +110,12 @@ export default {
this.onSwitch.call(null, this.slots()[index].key)
}
this.active = index
this.$refs.contents.scrollTop = 0
},
showNav () {
this.navMode = false
},
hideNav () {
this.navMode = true
}
},
render () {
@ -93,7 +123,7 @@ export default {
.map((slot, index) => {
const props = slot.props
if (!props) return
const classesTab = ['vertical-tab menu-item']
const classesTab = ['vertical-tab', 'menu-item']
if (this.activeIndex === index) {
classesTab.push('-active')
}
@ -104,6 +134,7 @@ export default {
class={classesTab.join(' ')}
type="button"
role="tab"
title={props.label}
>
{!props.icon ? '' : (<FAIcon class="tab-icon" size="1x" fixed-width icon={props.icon}/>)}
<span class="text">
@ -117,9 +148,9 @@ export default {
const props = slot.props
if (!props) return
const active = this.activeIndex === index
const classes = [ active ? 'active' : 'hidden' ]
const classes = ['tab-content-wrapper', active ? '-active' : '-hidden' ]
if (props.fullHeight) {
classes.push('full-height')
classes.push('-full-height')
}
let delayRender = slot.props['delay-render']
if (delayRender && active) {
@ -137,17 +168,25 @@ export default {
)
return (
<div ref="contents" class={classes}>
<div class={classes} >
{header}
{renderSlot}
<div class={ ['tab-content', props['full-width'] ? '-full-width' : null].join(' ') } >
{renderSlot}
</div>
</div>
)
})
const rootClasses = ['vertical-tab-switcher']
if (this.navMode) {
rootClasses.push('-nav-mode')
rootClasses.push('-nav-content')
}
return (
<div ref="root" class="vertical-tab-switcher">
<div ref="root" class={ rootClasses.join(' ') }>
<div
class="tabs -navigation-mode -tabs"
class="tabs"
role="tablist"
ref="nav"
>
@ -157,6 +196,7 @@ export default {
role="tabpanel"
class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}
v-body-scroll-lock={this.bodyScrollLock}
ref="contents"
>
{contents}
</div>

View file

@ -4,11 +4,13 @@
> .tabs {
flex: 0 0 auto;
overflow: hidden auto;
flex-direction: column;
overflow: hidden auto;
white-space: nowrap;
text-overflow: ellipsis;
width: 15em;
border-right: 1px solid;
border-right-color: var(--border);
min-width: 10em;
> .menu-item {
padding: 0.5em 1em;
@ -21,52 +23,69 @@
}
> .contents {
flex: 1 0 auto;
flex: 1 1 auto;
overflow-x: hidden;
.hidden {
display: none;
}
.tab-content {
align-self: center;
.full-height:not(.hidden) {
height: 100%;
display: flex;
flex-direction: column;
&:not(.-full-width) {
width: 30em;
}
> *:not(.tab-content-label) {
flex: 1;
&.-full-width {
align-self: stretch;
}
}
.tab-content-label {
font-size: 1.5em;
padding: 0.5em 1em;
margin: 0;
border-bottom: 1px solid var(--border);
.tab-content-wrapper {
display: flex;
flex-direction: column;
.tab-content-label {
font-size: 1.5em;
padding: 0.5em 1em;
margin: 0;
border-bottom: 1px solid var(--border);
}
&.-hidden {
display: none;
}
&.-full-height:not(.-hidden) {
height: 100%;
display: flex;
flex-direction: column;
> *:not(.tab-content-label) {
flex: 1;
}
}
}
}
> .tabs,
> .content {
transition: width 2s ease-in;
}
&.-nav-mode {
&.-nav-tabs {
> .tabs {
width: 100%;
}
&.-tabs {
> .tabs {
width: 100%;
> .nav-content {
width: 0%;
}
}
> .content {
width: 0%;
}
}
&.-nav-content {
> .tabs {
position: absolute;
pointer-events: none;
opacity: 0;
}
&.-content {
> .tabs {
width: 0%;
}
> .content {
width: 100%;
> .nav-content {
width: 100%;
}
}
}
}