Allow column width configuration

Group column configuration in settings
Column width configuration: do not act on defaults
This commit is contained in:
Alexander Tumin 2022-06-05 17:10:44 +03:00
parent 6b80ce122f
commit 3e7e31d4a9
16 changed files with 244 additions and 34 deletions

View File

@ -186,9 +186,13 @@ nav {
--columnGap: 1em; --columnGap: 1em;
--status-margin: 0.75em; --status-margin: 0.75em;
--effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
position: relative; position: relative;
display: grid; display: grid;
grid-template-columns: var(--miniColumn) var(--maxiColumn); grid-template-columns:
var(--sidebarColumnWidth, var(--miniColumn))
var(--effectiveContentColumnWidth);
grid-template-areas: "sidebar content"; grid-template-areas: "sidebar content";
grid-template-rows: 1fr; grid-template-rows: 1fr;
box-sizing: border-box; box-sizing: border-box;
@ -282,15 +286,24 @@ nav {
} }
&.-reverse:not(.-wide):not(.-mobile) { &.-reverse:not(.-wide):not(.-mobile) {
grid-template-columns: var(--maxiColumn) var(--miniColumn); grid-template-columns:
var(--effectiveContentColumnWidth)
minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
grid-template-areas: "content sidebar"; grid-template-areas: "content sidebar";
} }
&.-wide { &.-wide {
grid-template-columns: var(--miniColumn) var(--maxiColumn) var(--miniColumn); grid-template-columns:
minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)))
var(--effectiveContentColumnWidth)
minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
grid-template-areas: "sidebar content notifs"; grid-template-areas: "sidebar content notifs";
&.-reverse { &.-reverse {
grid-template-columns:
minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)))
var(--effectiveContentColumnWidth)
minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
grid-template-areas: "notifs content sidebar"; grid-template-areas: "notifs content sidebar";
} }
} }

View File

@ -12,7 +12,7 @@ import { windowWidth, windowHeight } from '../services/window_utils/window_utils
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import { applyTheme } from '../services/style_setter/style_setter.js' import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js' import FaviconService from '../services/favicon_service/favicon_service.js'
let staticInitialResults = null let staticInitialResults = null
@ -360,6 +360,8 @@ const afterStoreSetup = async ({ store, i18n }) => {
console.error('Failed to load any theme!') console.error('Failed to load any theme!')
} }
applyConfig(store.state.config)
// Now we can try getting the server settings and logging in // Now we can try getting the server settings and logging in
// Most of these are preloaded into the index.html so blocking is minimized // Most of these are preloaded into the index.html so blocking is minimized
await Promise.all([ await Promise.all([

View File

@ -42,6 +42,9 @@ export default {
methods: { methods: {
update (e) { update (e) {
set(this.$parent, this.path, e) set(this.$parent, this.path, e)
},
reset () {
set(this.$parent, this.path, this.defaultState)
} }
} }
} }

View File

@ -15,7 +15,12 @@
<slot /> <slot />
</span> </span>
{{ ' ' }} {{ ' ' }}
<ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox> <ModifiedIndicator
:changed="isChanged"
:onclick="reset"
/>
<ServerSideIndicator :server-side="isServerSide" />
</Checkbox>
</label> </label>
</template> </template>

View File

@ -43,6 +43,9 @@ export default {
methods: { methods: {
update (e) { update (e) {
set(this.$parent, this.path, e) set(this.$parent, this.path, e)
},
reset () {
set(this.$parent, this.path, this.defaultState)
} }
} }
} }

View File

@ -19,7 +19,10 @@
{{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }} {{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }}
</option> </option>
</Select> </Select>
<ModifiedIndicator :changed="isChanged" /> <ModifiedIndicator
:changed="isChanged"
:onclick="reset"
/>
<ServerSideIndicator :server-side="isServerSide" /> <ServerSideIndicator :server-side="isServerSide" />
</label> </label>
</template> </template>

View File

@ -36,6 +36,9 @@ export default {
methods: { methods: {
update (e) { update (e) {
set(this.$parent, this.path, parseInt(e.target.value)) set(this.$parent, this.path, parseInt(e.target.value))
},
reset () {
set(this.$parent, this.path, this.defaultState)
} }
} }
} }

View File

@ -17,7 +17,10 @@
@change="update" @change="update"
> >
{{ ' ' }} {{ ' ' }}
<ModifiedIndicator :changed="isChanged" /> <ModifiedIndicator
:changed="isChanged"
:onclick="reset"
/>
</span> </span>
</template> </template>

View File

@ -0,0 +1,67 @@
import { get, set } from 'lodash'
import ModifiedIndicator from './modified_indicator.vue'
import Select from 'src/components/select/select.vue'
export const allCssUnits = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%']
export const defaultHorizontalUnits = ['px', 'rem', 'vw']
export const defaultVerticalUnits = ['px', 'rem', 'vh']
export default {
components: {
ModifiedIndicator,
Select
},
props: {
path: String,
disabled: Boolean,
min: Number,
units: {
type: [String],
default: () => allCssUnits
},
expert: [Number, String]
},
computed: {
pathDefault () {
const [firstSegment, ...rest] = this.path.split('.')
return [firstSegment + 'DefaultValue', ...rest].join('.')
},
stateUnit () {
return (this.state || '').replace(/\d+/, '')
},
stateValue () {
return (this.state || '').replace(/\D+/, '')
},
state () {
const value = get(this.$parent, this.path)
if (value === undefined) {
return this.defaultState
} else {
return value
}
},
defaultState () {
return get(this.$parent, this.pathDefault)
},
isChanged () {
return this.state !== this.defaultState
},
matchesExpertLevel () {
return (this.expert || 0) <= this.$parent.expertLevel
}
},
methods: {
update (e) {
set(this.$parent, this.path, e)
},
reset () {
set(this.$parent, this.path, this.defaultState)
},
updateValue (e) {
set(this.$parent, this.path, parseInt(e.target.value) + this.stateUnit)
},
updateUnit (e) {
set(this.$parent, this.path, this.stateValue + e.target.value)
}
}
}

View File

@ -0,0 +1,54 @@
<template>
<span
v-if="matchesExpertLevel"
class="SizeSetting"
>
<label
:for="path"
class="size-label"
>
<slot />
</label>
<input
:id="path"
class="number-input"
type="number"
step="1"
:disabled="disabled"
:min="min || 0"
:value="stateValue"
@change="updateValue"
>
<Select
:id="path"
:model-value="stateUnit"
:disabled="disabled"
class="css-unit-input"
@change="updateUnit"
>
<option
v-for="option in units"
:key="option"
:value="option"
>
{{ option }}
</option>
</Select>
{{ ' ' }}
<ModifiedIndicator
:changed="isChanged"
:onclick="reset"
/>
</span>
</template>
<script src="./size_setting.js"></script>
<style lang="scss">
.css-unit-input, .css-unit-input select {
margin-left: 0.5em;
width: 4em !important;
max-width: 4em !important;
min-width: 4em !important;
}
</style>

View File

@ -2,6 +2,7 @@ import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue'
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue' import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
import IntegerSetting from '../helpers/integer_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue'
import SizeSetting, { defaultHorizontalUnits } from '../helpers/size_setting.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
@ -56,11 +57,15 @@ const GeneralTab = {
BooleanSetting, BooleanSetting,
ChoiceSetting, ChoiceSetting,
IntegerSetting, IntegerSetting,
SizeSetting,
InterfaceLanguageSwitcher, InterfaceLanguageSwitcher,
ScopeSelector, ScopeSelector,
ServerSideIndicator ServerSideIndicator
}, },
computed: { computed: {
horizontalUnits () {
return defaultHorizontalUnits
},
postFormats () { postFormats () {
return this.$store.state.instance.postFormats || [] return this.$store.state.instance.postFormats || []
}, },
@ -71,6 +76,17 @@ const GeneralTab = {
label: this.$t(`post_status.content_type["${format}"]`) label: this.$t(`post_status.content_type["${format}"]`)
})) }))
}, },
columns () {
const mode = this.$store.getters.mergedConfig.thirdColumnMode
const notif = mode === 'none' ? [] : ['notifs']
if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') {
return [...notif, 'content', 'sidebar']
} else {
return ['sidebar', 'content', ...notif]
}
},
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel }, instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
instanceWallpaperUsed () { instanceWallpaperUsed () {
return this.$store.state.instance.background && return this.$store.state.instance.background &&

View File

@ -15,11 +15,6 @@
{{ $t('settings.hide_isp') }} {{ $t('settings.hide_isp') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<BooleanSetting path="sidebarRight">
{{ $t('settings.right_sidebar') }}
</BooleanSetting>
</li>
<li v-if="instanceWallpaperUsed"> <li v-if="instanceWallpaperUsed">
<BooleanSetting path="hideInstanceWallpaper"> <BooleanSetting path="hideInstanceWallpaper">
{{ $t('settings.hide_wallpaper') }} {{ $t('settings.hide_wallpaper') }}
@ -64,16 +59,6 @@
{{ $t('settings.virtual_scrolling') }} {{ $t('settings.virtual_scrolling') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<BooleanSetting path="disableStickyHeaders">
{{ $t('settings.disable_sticky_headers') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="showScrollbars">
{{ $t('settings.show_scrollbars') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting <BooleanSetting
path="userPopoverZoom" path="userPopoverZoom"
@ -90,16 +75,6 @@
{{ $t('settings.user_popover_avatar_overlay') }} {{ $t('settings.user_popover_avatar_overlay') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<ChoiceSetting
v-if="user"
id="thirdColumnMode"
path="thirdColumnMode"
:options="thirdColumnModeOptions"
>
{{ $t('settings.third_column_mode') }}
</ChoiceSetting>
</li>
<li> <li>
<BooleanSetting <BooleanSetting
path="alwaysShowNewPostButton" path="alwaysShowNewPostButton"
@ -480,3 +455,16 @@
</template> </template>
<script src="./general_tab.js"></script> <script src="./general_tab.js"></script>
<style lang="scss">
.column-settings {
display: flex;
justify-content: space-evenly;
flex-wrap: wrap;
}
.column-settings .size-label {
display: block;
margin-bottom: 0.5em;
margin-top: 0.5em;
}
</style>

View File

@ -533,6 +533,11 @@
"third_column_mode_none": "Don't show third column at all", "third_column_mode_none": "Don't show third column at all",
"third_column_mode_notifications": "Notifications column", "third_column_mode_notifications": "Notifications column",
"third_column_mode_postform": "Main post form and navigation", "third_column_mode_postform": "Main post form and navigation",
"columns": "Columns",
"column_sizes": "Column sizes",
"column_sizes_sidebar": "Sidebar",
"column_sizes_content": "Content",
"column_sizes_notifs": "Notifications",
"tree_advanced": "Allow more flexible navigation in tree view", "tree_advanced": "Allow more flexible navigation in tree view",
"tree_fade_ancestors": "Display ancestors of the current status in faint text", "tree_fade_ancestors": "Display ancestors of the current status in faint text",
"conversation_display_linear": "Linear-style", "conversation_display_linear": "Linear-style",

View File

@ -456,6 +456,15 @@
"subject_line_mastodon": "Как в Mastodon: скопировать как есть", "subject_line_mastodon": "Как в Mastodon: скопировать как есть",
"subject_line_email": "Как в электронной почте: \"re: тема\"", "subject_line_email": "Как в электронной почте: \"re: тема\"",
"subject_line_behavior": "Копировать тему в ответах", "subject_line_behavior": "Копировать тему в ответах",
"third_column_mode": "Когда недостаточно места, показывать третью колонку содержащую",
"third_column_mode_none": "Не показывать третью колонку совсем",
"third_column_mode_notifications": "Колонку уведомлений",
"third_column_mode_postform": "Форму отправки сообщения и навигацию",
"columns": "Колонки",
"column_sizes": "Размеры колонок",
"column_sizes_sidebar": "Боковой",
"column_sizes_content": "Содержимого",
"column_sizes_notifs": "Уведомлений",
"no_mutes": "Нет игнорируемых", "no_mutes": "Нет игнорируемых",
"no_blocks": "Нет блокировок", "no_blocks": "Нет блокировок",
"notification_visibility_emoji_reactions": "Реакции", "notification_visibility_emoji_reactions": "Реакции",

View File

@ -1,5 +1,5 @@
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' import { setPreset, applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages' import messages from '../i18n/messages'
import localeService from '../services/locale/locale.service.js' import localeService from '../services/locale/locale.service.js'
@ -165,12 +165,17 @@ const config = {
setHighlight ({ commit, dispatch }, { user, color, type }) { setHighlight ({ commit, dispatch }, { user, color, type }) {
commit('setHighlight', { user, color, type }) commit('setHighlight', { user, color, type })
}, },
setOption ({ commit, dispatch }, { name, value }) { setOption ({ commit, dispatch, state }, { name, value }) {
commit('setOption', { name, value }) commit('setOption', { name, value })
switch (name) { switch (name) {
case 'theme': case 'theme':
setPreset(value) setPreset(value)
break break
case 'sidebarColumnWidth':
case 'contentColumnWidth':
case 'notifsColumnWidth':
applyConfig(state)
break
case 'customTheme': case 'customTheme':
case 'customThemeSource': case 'customThemeSource':
applyTheme(value) applyTheme(value)

View File

@ -1,6 +1,7 @@
import { convert } from 'chromatism' import { convert } from 'chromatism'
import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js' import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js'
import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js' import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js'
import { defaultState } from '../../modules/config.js'
export const applyTheme = (input) => { export const applyTheme = (input) => {
const { rules } = generatePreset(input) const { rules } = generatePreset(input)
@ -20,6 +21,36 @@ export const applyTheme = (input) => {
body.classList.remove('hidden') body.classList.remove('hidden')
} }
const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth }) =>
({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth })
const defaultConfigColumns = configColumns(defaultState)
export const applyConfig = (config) => {
const columns = configColumns(config)
if (columns === defaultConfigColumns) {
return
}
const head = document.head
const body = document.body
body.classList.add('hidden')
const rules = Object
.entries(columns)
.filter(([k, v]) => v)
.map(([k, v]) => `--${k}: ${v}`).join(';')
const styleEl = document.createElement('style')
head.appendChild(styleEl)
const styleSheet = styleEl.sheet
styleSheet.toString()
styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
body.classList.remove('hidden')
}
export const getCssShadow = (input, usesDropShadow) => { export const getCssShadow = (input, usesDropShadow) => {
if (input.length === 0) { if (input.length === 0) {
return 'none' return 'none'