Merge branch 'settings-modal' into shigusegubu

* settings-modal:
  fixes. sorry for bad commit message i'm tired
  lint
  Move modal frame parts away from modal-content into modal, improve error handling
  moved multiChoiceProperties where it fits better
This commit is contained in:
Henry Jameson 2020-05-27 12:39:13 +03:00
commit db2d3d9c8c
16 changed files with 301 additions and 234 deletions

View file

@ -1,19 +1,17 @@
<template>
<div class="error-window panel">
<div class="panel-heading">
<span class="title">
<div class="async-component-error">
<div>
<h4>
{{ $t('general.generic_error') }}
</span>
</div>
<div class="panel-body">
</h4>
<p>
{{ $t('general.error_retry') }}
</p>
<button
class="btn"
@click="closeAllModals"
@click="retry"
>
{{ $t('general.close') }}
{{ $t('general.retry') }}
</button>
</div>
</div>
@ -22,9 +20,7 @@
<script>
export default {
methods: {
closeAllModals () {
// TODO make a global hook to close all modals?
this.$store.dispatch('closeSettingsModal')
retry () {
this.$emit('resetAsyncComponent')
}
}
@ -32,7 +28,11 @@ export default {
</script>
<style lang="scss">
.error-window {
.async-component-error {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
.btn {
margin: .5em;
padding: .5em 2em;

View file

@ -1,16 +0,0 @@
<template>
<div class="big-spinner">
<i class="icon-spin4 animate-spin" />
</div>
</template>
<style lang="scss">
.big-spinner {
font-size: 15em;
line-height: 0;
opacity: .6;
> i {
color: white;
}
}
</style>

View file

@ -0,0 +1,29 @@
<template>
<div class="panel-loading">
<span class="loading-text">
<i class="icon-spin4 animate-spin" />
{{ $t('general.loading') }}
</span>
</div>
</template>
<style lang="scss">
@import 'src/_variables.scss';
.panel-loading {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
font-size: 2em;
color: $fallback--text;
color: var(--text, $fallback--text);
.loading-text i {
font-size: 3em;
line-height: 0;
vertical-align: middle;
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
</style>

View file

@ -1,6 +1,6 @@
import Modal from 'src/components/modal/modal.vue'
import BigSpinner from 'src/components/big_spinner/big_spinner.vue'
import ErrorWindow from 'src/components/error_window/error_window.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
const SettingsModal = {
@ -9,13 +9,24 @@ const SettingsModal = {
SettingsModalContent: getResettableAsyncComponent(
() => import('./settings_modal_content.vue'),
{
loading: BigSpinner,
error: ErrorWindow,
loading: PanelLoading,
error: AsyncComponentError,
delay: 0
}
)
},
methods: {
closeModal () {
this.$store.dispatch('closeSettingsModal')
},
peekModal () {
this.$store.dispatch('togglePeekSettingsModal')
}
},
computed: {
currentSaveStateNotice () {
return this.$store.state.interface.settings.currentSaveStateNotice
},
modalActivated () {
return this.$store.state.interface.settingsModalState !== 'hidden'
},

View file

@ -3,7 +3,7 @@
overflow: hidden;
&.peek {
.modal-panel {
.settings-modal-panel {
/* Explanation:
* Modal is positioned vertically centered.
* 100vh - 100% = Distance between modal's top+bottom boundaries and screen
@ -15,4 +15,30 @@
transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
}
}
.settings-modal-panel {
overflow: hidden;
transition: transform;
transition-timing-function: ease-in-out;
transition-duration: 300ms;
width: 1000px;
max-width: 90vw;
height: 90vh;
@media all and (max-width: 800px) {
max-width: 100vw;
height: 100vh;
}
.panel-body {
height: 100%;
overflow-y: hidden;
.btn {
min-height: 28px;
min-width: 10em;
padding: 0 2em;
}
}
}
}

View file

@ -5,10 +5,47 @@
:class="{ peek: modalPeeked }"
:no-background="modalPeeked"
>
<SettingsModalContent
v-if="modalActivated"
class="modal-panel"
/>
<div class="settings-modal-panel panel">
<div class="panel-heading">
<span class="title">
{{ $t('settings.settings') }}
</span>
<transition name="fade">
<template v-if="currentSaveStateNotice">
<div
v-if="currentSaveStateNotice.error"
class="alert error"
@click.prevent
>
{{ $t('settings.saving_err') }}
</div>
<div
v-if="!currentSaveStateNotice.error"
class="alert transparent"
@click.prevent
>
{{ $t('settings.saving_ok') }}
</div>
</template>
</transition>
<button
class="btn"
@click="peekModal"
>
{{ $t('general.peek') }}
</button>
<button
class="btn"
@click="closeModal"
>
{{ $t('general.close') }}
</button>
</div>
<div class="panel-body">
<SettingsModalContent v-if="modalActivated" />
</div>
</div>
</Modal>
</template>

View file

@ -25,20 +25,9 @@ const SettingsModalContent = {
ThemeTab
},
computed: {
currentSaveStateNotice () {
return this.$store.state.interface.settings.currentSaveStateNotice
},
isLoggedIn () {
return !!this.$store.state.users.currentUser
}
},
methods: {
closeModal () {
this.$store.dispatch('closeSettingsModal')
},
peekModal () {
this.$store.dispatch('togglePeekSettingsModal')
}
}
}

View file

@ -1,32 +1,6 @@
@import 'src/_variables.scss';
.settings-modal-panel {
overflow: hidden;
transition: transform;
transition-timing-function: ease-in-out;
transition-duration: 300ms;
width: 1000px;
max-width: 90vw;
height: 90vh;
@media all and (max-width: 800px) {
max-width: 100vw;
height: 100vh;
}
.settings_tab-switcher {
height: 100%;
}
.panel-body {
height: 100%;
overflow-y: hidden;
.btn {
min-height: 28px;
min-width: 10em;
padding: 0 2em;
}
}
.settings_tab-switcher {
height: 100%;
.full-height {
height: 100%;

View file

@ -1,112 +1,72 @@
<template>
<div class="settings-modal-panel panel">
<div class="panel-heading">
<span class="title">
{{ $t('settings.settings') }}
</span>
<transition name="fade">
<template v-if="currentSaveStateNotice">
<div
v-if="currentSaveStateNotice.error"
class="alert error"
@click.prevent
>
{{ $t('settings.saving_err') }}
</div>
<div
v-if="!currentSaveStateNotice.error"
class="alert transparent"
@click.prevent
>
{{ $t('settings.saving_ok') }}
</div>
</template>
</transition>
<button
class="btn"
@click="peekModal"
>
{{ $t('general.peek') }}
</button>
<button
class="btn"
@click="closeModal"
>
{{ $t('general.close') }}
</button>
<tab-switcher
ref="tabSwitcher"
class="settings_tab-switcher"
:side-tab-bar="true"
:scrollable-tabs="true"
>
<div
:label="$t('settings.general')"
icon="wrench"
>
<GeneralTab />
</div>
<div class="panel-body">
<tab-switcher
ref="tabSwitcher"
class="settings_tab-switcher"
:side-tab-bar="true"
:scrollable-tabs="true"
>
<div
:label="$t('settings.general')"
icon="wrench"
>
<GeneralTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.profile_tab')"
icon="user"
>
<ProfileTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.security_tab')"
icon="lock"
>
<SecurityTab />
</div>
<div
:label="$t('settings.filtering')"
icon="filter"
>
<FilteringTab />
</div>
<div
:label="$t('settings.theme')"
icon="brush"
>
<ThemeTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.notifications')"
icon="chat"
>
<NotificationsTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.data_import_export_tab')"
icon="download"
>
<DataImportExportTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.mutes_and_blocks')"
:fullHeight="true"
class="full-height"
icon="eye-off"
>
<MutesAndBlocksTab />
</div>
<div
:label="$t('settings.version.title')"
icon="info-circled"
>
<VersionTab />
</div>
</tab-switcher>
<div
v-if="isLoggedIn"
:label="$t('settings.profile_tab')"
icon="user"
>
<ProfileTab />
</div>
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.security_tab')"
icon="lock"
>
<SecurityTab />
</div>
<div
:label="$t('settings.filtering')"
icon="filter"
>
<FilteringTab />
</div>
<div
:label="$t('settings.theme')"
icon="brush"
>
<ThemeTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.notifications')"
icon="bell-ringing-o"
>
<NotificationsTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.data_import_export_tab')"
icon="download"
>
<DataImportExportTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.mutes_and_blocks')"
:fullHeight="true"
class="full-height"
icon="eye-off"
>
<MutesAndBlocksTab />
</div>
<div
:label="$t('settings.version.title')"
icon="info-circled"
>
<VersionTab />
</div>
</tab-switcher>
</template>
<script src="./settings_modal_content.js"></script>

View file

@ -1,10 +1,9 @@
import { filter, trim } from 'lodash'
import { instanceDefaultProperties, defaultState as configDefaultState } from 'src/modules/config.js'
const multiChoiceProperties = [
'postContentType',
'subjectLineBehavior'
]
import {
instanceDefaultProperties,
multiChoiceProperties,
defaultState as configDefaultState
} from 'src/modules/config.js'
const SharedComputedObject = () => ({
user () {

View file

@ -95,20 +95,25 @@
align-items: baseline;
width: 100%;
min-height: 30px;
.btn {
min-width: 1px;
flex: 0 auto;
padding: 0 1em;
}
margin-bottom: 1em;
p {
flex: 1;
margin: 0;
margin-right: .5em;
}
}
margin-bottom: 1em;
.tab-header-buttons {
display: flex;
flex-direction: column;
.btn {
min-width: 1px;
flex: 0 auto;
padding: 0 1em;
margin-bottom: .5em;
}
}
.shadow-selector {

View file

@ -126,18 +126,20 @@
>
<div class="tab-header">
<p>{{ $t('settings.theme_help') }}</p>
<button
class="btn"
@click="clearOpacity"
>
{{ $t('settings.style.switcher.clear_opacity') }}
</button>
<button
class="btn"
@click="clearV1"
>
{{ $t('settings.style.switcher.clear_all') }}
</button>
<div class="tab-header-buttons">
<button
class="btn"
@click="clearOpacity"
>
{{ $t('settings.style.switcher.clear_opacity') }}
</button>
<button
class="btn"
@click="clearV1"
>
{{ $t('settings.style.switcher.clear_all') }}
</button>
</div>
</div>
<p>{{ $t('settings.theme_help_v2_1') }}</p>
<h4>{{ $t('settings.style.common_colors.main') }}</h4>

View file

@ -95,9 +95,12 @@ export default Vue.component('tab-switcher', {
disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)}
class={classesTab.join(' ')}
type="button"
>
{!slot.data.attrs.icon ? '' : (<i class={'tab-icon icon-' + slot.data.attrs.icon}/>)}
{slot.data.attrs.label}
<span class="text">
{slot.data.attrs.label}
</span>
</button>
</div>
)
@ -110,13 +113,23 @@ export default Vue.component('tab-switcher', {
if (slot.data.attrs.fullHeight) {
classes.push('full-height')
}
const newSlot = (
<div class={classes}>
{
this.sideTabBar
? <h1 class="mobile-label">{slot.data.attrs.label}</h1>
: ''
}
{slot}
</div>
)
if (this.renderOnlyFocused) {
return active
? <div class={classes.join(' ')}>{slot}</div>
? <div class={classes.join(' ')}>{newSlot}</div>
: <div class={classes.join(' ')}></div>
}
return <div class={classes.join(' ')}>{slot}</div>
return <div class={classes.join(' ')}>{newSlot}</div>
})
return (

View file

@ -10,12 +10,14 @@
&.top-tabs {
flex-direction: column;
> .tabs {
width: 100%;
overflow-y: hidden;
overflow-x: auto;
padding-top: 5px;
flex-direction: row;
&::after, &::before {
content: '';
flex: 1 1 auto;
@ -51,34 +53,47 @@
&.side-tabs {
flex-direction: row;
@media all and (max-width: 800px) {
overflow-x: auto;
}
> .contents {
flex: 0 1 80%;
@media all and (max-width: 800px) {
min-width: 96vw;
}
flex: 1 1 auto;
}
> .tabs {
flex: 1 0 auto;
flex: 0 0 auto;
overflow-y: auto;
overflow-x: hidden;
flex-direction: column;
&::after, &::before {
flex: 1 0 .5em;
flex-shrink: 0;
flex-basis: .5em;
content: '';
border-right: 1px solid;
border-right-color: $fallback--border;
border-right-color: var(--border, $fallback--border);
}
&::after {
flex-grow: 1;
}
&::before {
flex-grow: 0;
}
.tab-wrapper {
min-width: 10em;
display: flex;
flex-direction: column;
@media all and (max-width: 800px) {
min-width: 1em;
}
&:not(.active)::after {
top: 0;
right: 0;
@ -87,6 +102,7 @@
border-right-color: $fallback--border;
border-right-color: var(--border, $fallback--border);
}
&::before {
flex: 0 0 6px;
content: '';
@ -94,6 +110,7 @@
border-right-color: $fallback--border;
border-right-color: var(--border, $fallback--border);
}
&:last-child .tab {
margin-bottom: 0;
}
@ -106,26 +123,23 @@
min-width: 1px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
padding-left: 1em;
padding-right: calc(1em + 200px);
margin-right: 6px - 200px;
margin-left: 6px;
}
.tab-wrapper {
min-width: 10em;
&:not(.active)::after {
top: 0;
right: 0;
bottom: 0;
border-right: 1px solid;
border-right-color: $fallback--border;
border-right-color: var(--border, $fallback--border);
margin-right: calc(1em - 200px);
margin-left: 1em;
@media all and (max-width: 800px) {
padding-left: .25em;
padding-right: calc(.25em + 200px);
margin-right: calc(.25em - 200px);
margin-left: .25em;
.text {
display: none
}
}
}
}
}
.contents {
flex: 1 0 auto;
min-height: 0px;
@ -142,14 +156,14 @@
.tab {
position: relative;
white-space: nowrap;
padding: 6px 1em;
background-color: $fallback--fg;
background-color: var(--tab, $fallback--fg);
&, &:active .tab-icon {
color: $fallback--text;
color: var(--tabText, $fallback--text);
}
background-color: $fallback--fg;
background-color: var(--tab, $fallback--fg);
&:not(.active) {
z-index: 4;
@ -173,7 +187,6 @@
}
}
.tabs {
display: flex;
position: relative;
@ -198,4 +211,17 @@
}
}
}
.mobile-label {
padding-left: .3em;
padding-bottom: .25em;
margin-top: .5em;
margin-left: .2em;
margin-bottom: .25em;
border-bottom: 1px solid var(--border, $fallback--border);
@media all and (min-width: 800px) {
display: none;
}
}
}

View file

@ -59,8 +59,10 @@
"apply": "Apply",
"submit": "Submit",
"more": "More",
"loading": "Loading…",
"generic_error": "An error occured",
"error_retry": "Please try again",
"retry": "Try again",
"optional": "optional",
"show_more": "Show more",
"show_less": "Show less",

View file

@ -3,6 +3,16 @@ import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
const browserLocale = (window.navigator.language || 'en').split('-')[0]
/* TODO this is a bit messy.
* We need to declare settings with their types and also deal with
* instance-default settings in some way, hopefully try to avoid copy-pasta
* in general.
*/
export const multiChoiceProperties = [
'postContentType',
'subjectLineBehavior'
]
export const defaultState = {
colors: {},
theme: undefined,