working prototype

This commit is contained in:
Henry Jameson 2025-11-24 20:05:38 +02:00
commit 7c57be22e4
15 changed files with 266 additions and 215 deletions

View file

@ -1,14 +1,26 @@
.settings-modal { .settings-modal {
overflow: hidden; overflow: hidden;
h3 { h2 {
font-size: 1.4rem; font-size: 1.3rem;
font-weight: 500;
margin-top: 1em; margin-top: 1em;
margin-bottom: 1em; margin-bottom: 1em;
} }
h4 { h3 {
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 500;
margin-top: 1em;
margin-bottom: 0.5em;
border-bottom: 1px solid var(--border);
padding-bottom: 0.25em;
box-sizing: border-box;
padding-left: 0.5em;
}
h4 {
font-size: 1.1rem;
margin-top: 1em; margin-top: 1em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@ -16,19 +28,21 @@
h5 { h5 {
font-size: 1rem; font-size: 1rem;
margin-bottom: 0.5em; margin-bottom: 0.5em;
margin-top: 0;
} }
.setting-list, .setting-list,
.option-list { .option-list {
list-style-type: none; list-style-type: none;
padding-left: 2em; padding-left: 2em;
margin: 0;
.btn:not(.dropdown-button) { .btn:not(.dropdown-button) {
padding: 0 2em; padding: 0 2em;
} }
li { li {
margin-bottom: 0.5em; margin: 1em 0;
} }
.suboptions { .suboptions {
@ -55,7 +69,7 @@
transition: transform; transition: transform;
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
transition-duration: 300ms; transition-duration: 300ms;
width: 1000px; width: 70em;
max-width: 90vw; max-width: 90vw;
height: 90vh; height: 90vh;
@ -90,6 +104,12 @@
} }
&.-mobile { &.-mobile {
.tabs {
.menu-item {
font-size: 1.2em
}
}
.setting-list, .setting-list,
.option-list { .option-list {
padding-left: 0.25em; padding-left: 0.25em;

View file

@ -82,9 +82,6 @@ const SettingsModalContent = {
}, },
expertLevel () { expertLevel () {
return this.$store.state.config.expertLevel return this.$store.state.config.expertLevel
},
isMobileLayout () {
return useInterfaceStore().layoutType === 'mobile'
} }
}, },
data () { data () {
@ -108,21 +105,6 @@ const SettingsModalContent = {
// Clear the state of target tab, so that next time settings is opened // Clear the state of target tab, so that next time settings is opened
// it doesn't force it. // it doesn't force it.
useInterfaceStore().clearSettingsModalTargetTab() useInterfaceStore().clearSettingsModalTargetTab()
},
nestedTooBig () {
if (this.navCollapsed) {
this.navCollapsed = false
this.$refs.tabSwitcher.showNav()
}
},
nestedTooSmall () {
if (!this.navCollapsed) {
this.navCollapsed = true
this.$refs.tabSwitcher.hideNav()
}
},
nestedNavSide (side) {
this.navHideHeader = side === 'content'
} }
}, },
mounted () { mounted () {

View file

@ -1,20 +1,6 @@
.settings_tab-switcher { .settings_tab-switcher {
height: 100%; height: 100%;
h1 {
margin-bottom: 0.5em;
margin-top: 0.5em;
}
h4 {
margin-bottom: 0;
margin-top: 0.25em;
}
h5 {
margin-top: 0.25em;
}
.setting-item { .setting-item {
border-bottom: 2px solid var(--border); border-bottom: 2px solid var(--border);
margin: 1em 1em 1.4em; margin: 1em 1em 1.4em;

View file

@ -8,7 +8,6 @@
:hide-header="navHideHeader" :hide-header="navHideHeader"
> >
<div <div
:full-width="true"
:label="$t('settings.general')" :label="$t('settings.general')"
icon="wrench" icon="wrench"
data-tab-name="general" data-tab-name="general"
@ -16,16 +15,15 @@
<GeneralTab /> <GeneralTab />
</div> </div>
<div <div
v-if="isLoggedIn"
:label="$t('settings.profile_tab')"
icon="user"
data-tab-name="profile"
:full-width="true" :full-width="true"
:label="$t('settings.posts')"
icon="message"
data-tab-name="posts"
:delay-render="true"
> >
<PostsTab /> <ProfileTab />
</div> </div>
<div <div
:full-width="true"
:label="$t('settings.composing')" :label="$t('settings.composing')"
icon="pen-alt" icon="pen-alt"
data-tab-name="composing" data-tab-name="composing"
@ -34,13 +32,12 @@
<ComposingTab /> <ComposingTab />
</div> </div>
<div <div
:full-width="true" :label="$t('settings.posts')"
:label="$t('settings.layout')" icon="message"
icon="table-columns" data-tab-name="posts"
data-tab-name="layout"
:delay-render="true" :delay-render="true"
> >
<LayoutTab /> <PostsTab />
</div> </div>
<div <div
:full-width="true" :full-width="true"
@ -52,39 +49,26 @@
<AppearanceTab /> <AppearanceTab />
</div> </div>
<div <div
v-if="isLoggedIn" :full-width="true"
:label="$t('settings.profile_tab')" :label="$t('settings.layout')"
icon="user" icon="table-columns"
data-tab-name="profile" data-tab-name="layout"
:delay-render="true"
> >
<ProfileTab /> <LayoutTab />
</div> </div>
<div <div
v-if="isLoggedIn" v-if="isLoggedIn"
:full-width="true"
:label="$t('settings.notifications')" :label="$t('settings.notifications')"
icon="bell" icon="bell"
data-tab-name="notifications" data-tab-name="notifications"
> >
<NotificationsTab /> <NotificationsTab />
</div> </div>
<div
v-if="isLoggedIn"
:label="$t('settings.security_tab')"
icon="lock"
data-tab-name="security"
>
<SecurityTab />
</div>
<div
:label="$t('settings.clutter')"
:fullHeight="true"
icon="broom"
data-tab-name="mutesAndBlocks"
>
<ClutterTab />
</div>
<div <div
:label="$t('settings.filtering')" :label="$t('settings.filtering')"
:full-width="true"
icon="filter" icon="filter"
data-tab-name="filtering" data-tab-name="filtering"
> >
@ -99,6 +83,22 @@
> >
<MutesAndBlocksTab /> <MutesAndBlocksTab />
</div> </div>
<div
:label="$t('settings.clutter')"
:fullHeight="true"
icon="broom"
data-tab-name="clutter"
>
<ClutterTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.security_tab')"
icon="lock"
data-tab-name="security"
>
<SecurityTab />
</div>
<div <div
v-if="isLoggedIn" v-if="isLoggedIn"
:label="$t('settings.data_import_export_tab')" :label="$t('settings.data_import_export_tab')"
@ -123,7 +123,7 @@
data-tab-name="theme" data-tab-name="theme"
:delay-render="true" :delay-render="true"
> >
<ThemeTab /> <OldThemeTab />
</div> </div>
<div <div
v-if="expertLevel > 0" v-if="expertLevel > 0"

View file

@ -1,4 +1,8 @@
.appearance-tab { .appearance-tab {
h3 {
border: none
}
.palette, .palette,
.theme-notice { .theme-notice {
padding: 0.5em; padding: 0.5em;
@ -10,14 +14,6 @@
padding-bottom: 0.5em; padding-bottom: 0.5em;
} }
h4 {
margin: 2em 0;
line-height: 1.5;
}
h5 {
display: block;
}
input[type="file"] { input[type="file"] {
padding: 0.25em; padding: 0.25em;

View file

@ -0,0 +1,5 @@
.data-import-export-tab {
h3 {
margin-right: -2em;
}
}

View file

@ -1,9 +1,10 @@
<template> <template>
<div <div
class="data-import-export-tab"
:label="$t('settings.data_import_export_tab')" :label="$t('settings.data_import_export_tab')"
> >
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.follow_import') }}</h2> <h3>{{ $t('settings.follow_import') }}</h3>
<p>{{ $t('settings.import_followers_from_a_csv_file') }}</p> <p>{{ $t('settings.import_followers_from_a_csv_file') }}</p>
<Importer <Importer
:submit-handler="importFollows" :submit-handler="importFollows"
@ -12,7 +13,7 @@
/> />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.follow_export') }}</h2> <h3>{{ $t('settings.follow_export') }}</h3>
<Exporter <Exporter
:get-content="getFollowsContent" :get-content="getFollowsContent"
filename="friends.csv" filename="friends.csv"
@ -20,7 +21,7 @@
/> />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.block_import') }}</h2> <h3>{{ $t('settings.block_import') }}</h3>
<p>{{ $t('settings.import_blocks_from_a_csv_file') }}</p> <p>{{ $t('settings.import_blocks_from_a_csv_file') }}</p>
<Importer <Importer
:submit-handler="importBlocks" :submit-handler="importBlocks"
@ -29,7 +30,7 @@
/> />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.block_export') }}</h2> <h3>{{ $t('settings.block_export') }}</h3>
<Exporter <Exporter
:get-content="getBlocksContent" :get-content="getBlocksContent"
filename="blocks.csv" filename="blocks.csv"
@ -37,7 +38,7 @@
/> />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.mute_import') }}</h2> <h3>{{ $t('settings.mute_import') }}</h3>
<p>{{ $t('settings.import_mutes_from_a_csv_file') }}</p> <p>{{ $t('settings.import_mutes_from_a_csv_file') }}</p>
<Importer <Importer
:submit-handler="importMutes" :submit-handler="importMutes"
@ -46,7 +47,7 @@
/> />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.mute_export') }}</h2> <h3>{{ $t('settings.mute_export') }}</h3>
<Exporter <Exporter
:get-content="getMutesContent" :get-content="getMutesContent"
filename="mutes.csv" filename="mutes.csv"
@ -54,7 +55,7 @@
/> />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.account_backup') }}</h2> <h3>{{ $t('settings.account_backup') }}</h3>
<p>{{ $t('settings.account_backup_description') }}</p> <p>{{ $t('settings.account_backup_description') }}</p>
<table> <table>
<thead> <thead>
@ -128,4 +129,4 @@
</template> </template>
<script src="./data_import_export_tab.js"></script> <script src="./data_import_export_tab.js"></script>
<!-- <style lang="scss" src="./profile.scss"></style> --> <style lang="scss" src="./data_import_export_tab.scss"></style>

View file

@ -1,3 +1,9 @@
import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/' const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
const VersionTab = { const VersionTab = {
@ -9,10 +15,14 @@ const VersionTab = {
frontendVersion: instance.frontendVersion frontendVersion: instance.frontendVersion
} }
}, },
components: {
BooleanSetting
},
computed: { computed: {
frontendVersionLink () { frontendVersionLink () {
return pleromaFeCommitUrl + this.frontendVersion return pleromaFeCommitUrl + this.frontendVersion
} },
...SharedComputedObject(),
} }
} }

View file

@ -0,0 +1,10 @@
.developer-tab {
dt {
font-weight: 900;
}
dd {
margin-top: 0.5em;
margin-bottom: 1em;
}
}

View file

@ -1,32 +1,31 @@
<template> <template>
<div :label="$t('settings.developer')"> <div
<div class="setting-item"> :label="$t('settings.developer')"
<ul class="setting-list"> class="developer-tab"
<li> >
<p>{{ $t('settings.version.backend_version') }}</p>
<ul class="option-list">
<li>
<a
:href="backendRepository"
target="_blank"
>{{ backendVersion }}</a>
</li>
</ul>
</li>
<li>
<p>{{ $t('settings.version.frontend_version') }}</p>
<ul class="option-list">
<li>
<a
:href="frontendVersionLink"
target="_blank"
>{{ frontendVersion }}</a>
</li>
</ul>
</li>
</ul>
</div>
<div class="setting-item"> <div class="setting-item">
<h3>{{ $t('settings.version.title')}}</h3>
<dl class="setting-list">
<dt>{{ $t('settings.version.backend_version') }}</dt>
<dd>
<a
:href="backendRepository"
target="_blank"
>
{{ backendVersion }}
</a>
</dd>
<dt>{{ $t('settings.version.frontend_version') }}</dt>
<dd>
<a
:href="frontendVersionLink"
target="_blank"
>
{{ frontendVersion }}
</a>
</dd>
</dl>
<h3>{{ $t('settings.debug')}}</h3>
<ul class="setting-list"> <ul class="setting-list">
<li> <li>
<BooleanSetting path="virtualScrolling"> <BooleanSetting path="virtualScrolling">
@ -70,3 +69,4 @@
</div> </div>
</template> </template>
<script src="./developer_tab.js" /> <script src="./developer_tab.js" />
<style lang="scss" src="./developer_tab.scss"></style>

View file

@ -233,9 +233,11 @@
{{ $t('settings.use_contain_fit') }} {{ $t('settings.use_contain_fit') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<h3 v-if="expertLevel > 0"> </ul>
{{ $t('settings.fun') }} <h3 v-if="expertLevel > 0">
</h3> {{ $t('settings.fun') }}
</h3>
<ul class="setting-list">
<li v-if="user"> <li v-if="user">
<BooleanSetting <BooleanSetting
path="mentionLinkShowYous" path="mentionLinkShowYous"

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="profile-tab"> <div class="profile-tab">
<div class="setting-item profile-edit"> <div class="setting-item profile-edit">
<h2>{{ $t('settings.account_profile_edit') }}</h2> <h3>{{ $t('settings.account_profile_edit') }}</h3>
<UserCard <UserCard
:user-id="user.id" :user-id="user.id"
:editable="true" :editable="true"
@ -9,7 +9,7 @@
/> />
</div> </div>
<div class="setting-item"> <div class="setting-item">
<h2>{{ $t('settings.account_privacy') }}</h2> <h3>{{ $t('settings.account_privacy') }}</h3>
<ul class="setting-list"> <ul class="setting-list">
<li> <li>
<Checkbox v-model="locked"> <Checkbox v-model="locked">

View file

@ -37,19 +37,13 @@ export default {
required: false, required: false,
type: Boolean, type: Boolean,
default: null default: null
},
hideHeader: {
required: false,
type: Boolean,
default: null
} }
}, },
data () { data () {
return { return {
active: findFirstUsable(this.slots()), active: findFirstUsable(this.slots()),
resizeHandler: null, resizeHandler: null,
navMode: false, navSide: 'tabs'
navSide: 'content'
} }
}, },
computed: { computed: {
@ -71,16 +65,6 @@ export default {
mobileLayout: store => store.layoutType === 'mobile' 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 () { beforeUpdate () {
const currentSlot = this.slots()[this.active] const currentSlot = this.slots()[this.active]
if (!currentSlot.props) { if (!currentSlot.props) {
@ -92,7 +76,6 @@ export default {
return (e) => { return (e) => {
e.preventDefault() e.preventDefault()
this.setTab(index) this.setTab(index)
this.onResize()
} }
}, },
setTab (index) { setTab (index) {
@ -105,30 +88,6 @@ export default {
changeNavSide (side) { changeNavSide (side) {
if (this.navSide !== side) { if (this.navSide !== side) {
this.navSide = side this.navSide = side
this.onResize()
}
},
getNavMode () {
return this.navMode
},
onResize () {
// All other tabs are hidden and their width is most likely 0
const activeTab = this.$refs.root.querySelector('.tab-content-wrapper.-active')
const tabContent = activeTab.querySelector('.tab-content')
const tabContentWidth = tabContent.clientWidth
const rootWidth = this.$refs.root.clientWidth
const navWidth = this.$refs.nav.clientWidth
const contentsWidth = rootWidth - navWidth
// if contents takes more space than its container
if (contentsWidth < tabContentWidth) {
this.hideNav()
// If we (theoretically) have enough space to fit it in
} else if (contentsWidth - navWidth >= tabContentWidth){
// First expand the inner layer, then outer
// if use same logic as above order will be reversed
this.showNav()
} }
}, },
// DO NOT put it to computed, it doesn't work (caching?) // DO NOT put it to computed, it doesn't work (caching?)
@ -145,7 +104,7 @@ export default {
const props = slot.props const props = slot.props
if (!props) return if (!props) return
const classesTab = ['vertical-tab', 'menu-item'] const classesTab = ['vertical-tab', 'menu-item']
if (this.activeIndex === index) { if (this.activeIndex === index && useInterfaceStore().layoutType !== 'mobile') {
classesTab.push('-active') classesTab.push('-active')
} }
return ( return (
@ -183,14 +142,21 @@ export default {
: '' : ''
const headerClasses = ['tab-content-label'] const headerClasses = ['tab-content-label']
if (this.hideHeader === true) {
headerClasses.push('-hidden')
}
const header = ( const header = (
<h1 class={headerClasses}> <h2 class={headerClasses}>
<button type="button" onClick={() => this.changeNavSide('tabs')}>LOL</button> <button
type="button"
onClick={() => this.changeNavSide('tabs')}
class="button-unstyled"
>
<FAIcon
size="lg"
class="back-button-icon"
icon="chevron-left"
/>
</button>
{props.label} {props.label}
</h1> </h2>
) )
return ( return (
@ -204,13 +170,14 @@ export default {
}) })
const rootClasses = ['vertical-tab-switcher'] const rootClasses = ['vertical-tab-switcher']
if (this.navMode) { if (useInterfaceStore().layoutType === 'mobile') {
rootClasses.push('-nav-mode') rootClasses.push('-mobile')
if (this.navSide === 'content') { }
rootClasses.push('-nav-content')
} else { if (this.navSide === 'tabs') {
rootClasses.push('-nav-tabs') rootClasses.push('-nav-tabs')
} } else {
rootClasses.push('-nav-contents')
} }
return ( return (

View file

@ -1,16 +1,19 @@
.vertical-tab-switcher { .vertical-tab-switcher {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
container-type: inline-size;
> .tabs { > .tabs {
flex: 0 0 auto; flex: 0 0 15em;
flex-direction: column; flex-direction: column;
overflow: hidden auto; overflow: hidden auto;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
width: 15em; width: 15em;
min-width: 15em;
border-right: 1px solid; border-right: 1px solid;
border-right-color: var(--border); border-right-color: var(--border);
box-sizing: border-box;
> .menu-item { > .menu-item {
padding: 0.5em 1em; padding: 0.5em 1em;
@ -23,18 +26,13 @@
} }
> .contents { > .contents {
flex: 1 1 auto; flex: 1 0 35em;
overflow-x: hidden;
.tab-content-label {
display: none
}
.tab-content { .tab-content {
align-self: center; align-self: center;
&:not(.-full-width) { &:not(.-full-width) {
width: 30em; max-width: 40em;
} }
&.-full-width { &.-full-width {
@ -42,16 +40,23 @@
} }
} }
.tab-content-label {
box-sizing: border-box;
margin: 0;
border-bottom: 1px solid var(--border);
display: none;
button {
box-sizing: border-box;
padding: 0.5em;
}
}
.tab-content-wrapper { .tab-content-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: auto;
.tab-content-label { height: 100%;
font-size: 1.5em;
padding: 0.5em 1em;
margin: 0;
border-bottom: 1px solid var(--border);
}
&.-hidden { &.-hidden {
display: none; display: none;
@ -69,38 +74,104 @@
} }
} }
&.-nav-mode { &.-mobile {
&.-nav-tabs { > .contents {
> .tabs { .tab-content-label {
width: 100%; display: block
}
> .nav-content {
width: 0%;
} }
} }
&.-nav-content { &.-nav-contents {
> .contents { > .contents {
> .tab-content-wrapper { display: block;
> .tab-content-label { flex-grow: 1;
display: block; }
&.-hidden { > .tabs {
display: none; display: none;
} flex-grow: 0;
flex-shrink: 1;
}
}
&.-nav-tabs {
> .tabs {
display: block;
flex-grow: 1;
}
> .contents {
display: none;
flex-grow: 0;
flex-shrink: 1;
}
}
}
@supports (container-type: inline-size) {
&,
&.-mobile {
&.-nav-contents,
&.-nav-tabs {
/* I THINK it's a false positive and eslint doesn't understand the @-rule */
/* stylelint-disable no-descending-specificity */
> .contents {
display: block;
flex-grow: 1;
}
> .tabs {
display: block;
flex-grow: 0;
}
/* stylelint-enable no-descending-specificity */
}
}
@container (width < 50em) {
> .contents {
.tab-content-label {
display: block
}
}
&.-mobile {
> .contents {
.tab-content-label {
display: block
} }
} }
} }
> .tabs { &,
position: absolute; &.-mobile {
pointer-events: none; &.-nav-contents {
opacity: 0; > .contents {
} display: block;
flex-grow: 1;
}
> .nav-content { > .tabs {
width: 100%; display: none;
flex-grow: 0;
flex-shrink: 1;
}
}
&.-nav-tabs {
/* stylelint-disable no-descending-specificity */
> .tabs {
display: block;
flex-grow: 1;
}
> .contents {
display: none;
flex-grow: 0;
flex-shrink: 1;
}
/* stylelint-enable no-descending-specificity */
}
} }
} }
} }

View file

@ -405,6 +405,7 @@
"post_look_feel": "Posts Look & Feel", "post_look_feel": "Posts Look & Feel",
"posts": "Posts", "posts": "Posts",
"developer": "Developer", "developer": "Developer",
"debug": "Debug",
"mention_links": "Mention links", "mention_links": "Mention links",
"appearance": "Appearance", "appearance": "Appearance",
"confirm_new_setting": "Confirm new setting?", "confirm_new_setting": "Confirm new setting?",
@ -418,7 +419,7 @@
"navbar_size": "Top bar size", "navbar_size": "Top bar size",
"panel_header_size": "Panel header size", "panel_header_size": "Panel header size",
"visual_tweaks": "Minor visual tweaks", "visual_tweaks": "Minor visual tweaks",
"theme_debug": "Show what background theme engine assumes when dealing with transparancy (DEBUG)", "theme_debug": "Show what background theme engine assumes when dealing with transparancy",
"scale_and_layout": "Interface scale and layout", "scale_and_layout": "Interface scale and layout",
"timelines": "Timelines", "timelines": "Timelines",
"format_and_language": "Format and Language", "format_and_language": "Format and Language",
@ -753,7 +754,7 @@
"subject_line_email": "Like email: \"re: subject\"", "subject_line_email": "Like email: \"re: subject\"",
"subject_line_mastodon": "Like mastodon: copy as is", "subject_line_mastodon": "Like mastodon: copy as is",
"subject_line_noop": "Do not copy", "subject_line_noop": "Do not copy",
"force_theme_recompilation_debug": "Disable theme cahe, force recompile on each boot (DEBUG)", "force_theme_recompilation_debug": "Disable theme cahe, force recompile on each boot",
"conversation_display": "Conversation display style", "conversation_display": "Conversation display style",
"conversation_display_tree": "Tree-style", "conversation_display_tree": "Tree-style",
"conversation_display_tree_quick": "Tree view", "conversation_display_tree_quick": "Tree view",