diff options
| -rw-r--r-- | src/components/export_import/export_import.vue | 75 | ||||
| -rw-r--r-- | src/components/style_switcher/preview.vue | 78 | ||||
| -rw-r--r-- | src/components/style_switcher/style_switcher.js | 97 | ||||
| -rw-r--r-- | src/components/style_switcher/style_switcher.vue | 93 | ||||
| -rw-r--r-- | src/modules/config.js | 4 |
5 files changed, 200 insertions, 147 deletions
diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue new file mode 100644 index 00000000..9914d54a --- /dev/null +++ b/src/components/export_import/export_import.vue @@ -0,0 +1,75 @@ +<template> +<div class="import-export"> + <button class="btn" @click="exportData">{{ exportLabel }}</button> + <button class="btn" @click="importData">{{ importLabel }}</button> + <p v-if="importFailed" class="import-warning">{{ importFailedText }}</p> +</div> +</template> + +<script> +export default { + props: [ + 'exportObject', + 'importLabel', + 'exportLabel', + 'importFailedText', + 'validator', + 'onImport', + 'onImportFailure' + ], + data () { + return { + importFailed: false + } + }, + methods: { + exportData () { + const stringified = JSON.stringify(this.exportObject) // Pretty-print and indent with 2 spaces + + // Create an invisible link with a data url and simulate a click + const e = document.createElement('a') + e.setAttribute('download', 'pleroma_theme.json') + e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified)) + e.style.display = 'none' + + document.body.appendChild(e) + e.click() + document.body.removeChild(e) + }, + importData () { + this.importFailed = false + const filePicker = document.createElement('input') + filePicker.setAttribute('type', 'file') + filePicker.setAttribute('accept', '.json') + + filePicker.addEventListener('change', event => { + if (event.target.files[0]) { + // eslint-disable-next-line no-undef + const reader = new FileReader() + reader.onload = ({target}) => { + try { + const parsed = JSON.parse(target.result) + const valid = this.validator(parsed) + if (valid) { + this.onImport(parsed) + } else { + this.importFailed = true + // this.onImportFailure(valid) + } + } catch (e) { + // This will happen both if there is a JSON syntax error or the theme is missing components + this.importFailed = true + // this.onImportFailure(e) + } + } + reader.readAsText(event.target.files[0]) + } + }) + + document.body.appendChild(filePicker) + filePicker.click() + document.body.removeChild(filePicker) + } + } +} +</script> diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue new file mode 100644 index 00000000..09a136e9 --- /dev/null +++ b/src/components/style_switcher/preview.vue @@ -0,0 +1,78 @@ +<template> +<div class="panel dummy"> + <div class="panel-heading"> + <div class="title"> + {{$t('settings.style.preview.header')}} + <span class="badge badge-notification"> + 99 + </span> + </div> + <span class="faint"> + {{$t('settings.style.preview.header_faint')}} + </span> + <span class="alert error"> + {{$t('settings.style.preview.error')}} + </span> + <button class="btn"> + {{$t('settings.style.preview.button')}} + </button> + </div> + <div class="panel-body theme-preview-content"> + <div class="post"> + <div class="avatar"> + ( ͡° ͜ʖ ͡°) + </div> + <div class="content"> + <h4> + {{$t('settings.style.preview.content')}} + </h4> + + <i18n path="settings.style.preview.text"> + <code style="font-family: var(--postCodeFont)"> + {{$t('settings.style.preview.mono')}} + </code> + <a style="color: var(--link)"> + {{$t('settings.style.preview.link')}} + </a> + </i18n> + + <div class="icons"> + <i style="color: var(--cBlue)" class="icon-reply"/> + <i style="color: var(--cGreen)" class="icon-retweet"/> + <i style="color: var(--cOrange)" class="icon-star"/> + <i style="color: var(--cRed)" class="icon-cancel"/> + </div> + </div> + </div> + + <div class="after-post"> + <div class="avatar-alt"> + :^) + </div> + <div class="content"> + <i18n path="settings.style.preview.fine_print" tag="span" class="faint"> + <a style="color: var(--faintLink)"> + {{$t('settings.style.preview.faint_link')}} + </a> + </i18n> + </div> + </div> + <div class="separator"></div> + + <span class="alert error"> + {{$t('settings.style.preview.error')}} + </span> + <input :value="$t('settings.style.preview.input')" type="text"> + + <div class="actions"> + <span class="checkbox"> + <input checked="very yes" type="checkbox" id="preview_checkbox"> + <label for="preview_checkbox">{{$t('settings.style.preview.checkbox')}}</label> + </span> + <button class="btn"> + {{$t('settings.style.preview.button')}} + </button> + </div> + </div> +</div> +</template> diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index adcbee25..50cd1e6f 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -8,6 +8,8 @@ import ShadowControl from '../shadow_control/shadow_control.vue' import FontControl from '../font_control/font_control.vue' import ContrastRatio from '../contrast_ratio/contrast_ratio.vue' import TabSwitcher from '../tab_switcher/tab_switcher.jsx' +import Preview from './preview.vue' +import ExportImport from '../export_import/export_import.vue' // List of color values used in v1 const v1OnlyNames = [ @@ -26,7 +28,6 @@ export default { return { availableStyles: [], selected: this.$store.state.config.theme, - invalidThemeImported: false, previewShadows: {}, previewColors: {}, @@ -293,20 +294,11 @@ export default { }, themeValid () { return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid - } - }, - components: { - ColorInput, - OpacityInput, - RangeInput, - ContrastRatio, - ShadowControl, - FontControl, - TabSwitcher - }, - methods: { - exportCurrentTheme () { + }, + exportedTheme () { const saveEverything = !this.keepFonts && !this.keepShadows && !this.keepColors && !this.keepOpacity && !this.keepRoundness + + // TODO change into delete-less version. const theme = { shadows: this.shadowsLocal, fonts: this.fontsLocal, @@ -331,57 +323,24 @@ export default { delete theme.radii } - const stringified = JSON.stringify({ + return { // To separate from other random JSON files and possible future theme formats _pleroma_theme_version: 2, theme - }, null, 2) // Pretty-print and indent with 2 spaces - - // Create an invisible link with a data url and simulate a click - const e = document.createElement('a') - e.setAttribute('download', 'pleroma_theme.json') - e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified)) - e.style.display = 'none' - - document.body.appendChild(e) - e.click() - document.body.removeChild(e) - }, - - importTheme () { - this.invalidThemeImported = false - const filePicker = document.createElement('input') - filePicker.setAttribute('type', 'file') - filePicker.setAttribute('accept', '.json') - - filePicker.addEventListener('change', event => { - if (event.target.files[0]) { - // eslint-disable-next-line no-undef - const reader = new FileReader() - reader.onload = ({target}) => { - try { - const parsed = JSON.parse(target.result) - if (parsed._pleroma_theme_version === 1) { - this.normalizeLocalState(parsed, 1) - } else if (parsed._pleroma_theme_version === 2) { - this.normalizeLocalState(parsed.theme, 2) - } else { - // A theme from the future, spooky - this.invalidThemeImported = true - } - } catch (e) { - // This will happen both if there is a JSON syntax error or the theme is missing components - this.invalidThemeImported = true - } - } - reader.readAsText(event.target.files[0]) - } - }) - - document.body.appendChild(filePicker) - filePicker.click() - document.body.removeChild(filePicker) - }, - + } + } + }, + components: { + ColorInput, + OpacityInput, + RangeInput, + ContrastRatio, + ShadowControl, + FontControl, + TabSwitcher, + Preview, + ExportImport + }, + methods: { setCustomTheme () { this.$store.dispatch('setOption', { name: 'customTheme', @@ -394,7 +353,17 @@ export default { } }) }, - + onImport (parsed) { + if (parsed._pleroma_theme_version === 1) { + this.normalizeLocalState(parsed, 1) + } else if (parsed._pleroma_theme_version === 2) { + this.normalizeLocalState(parsed.theme, 2) + } + }, + importValidator (parsed) { + const version = parsed._pleroma_theme_version + return version >= 1 || version <= 2 + }, clearAll () { const state = this.$store.state.config.customTheme const version = state.colors ? 2 : 'l1' diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 9de60f7b..730bfef0 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -18,11 +18,14 @@ <i class="icon-down-open"/> </label> </div> - <div class="import-export"> - <button class="btn" @click="exportCurrentTheme">{{ $t('settings.export_theme') }}</button> - <button class="btn" @click="importTheme">{{ $t('settings.import_theme') }}</button> - <p v-if="invalidThemeImported" class="import-warning">{{ $t('settings.invalid_theme_imported') }}</p> - </div> + <export-import + :exportObject='exportedTheme' + :exportLabel='$t("settings.export_theme")' + :importLabel='$t("settings.import_theme")' + :importFailedText='$t("settings.invalid_theme_imported")' + :onImport='onImport' + :validator='importValidator' + /> </div> <div class="save-load-options"> <span> @@ -58,82 +61,7 @@ </div> <div class="preview-container"> - <div class="panel dummy" :style="previewRules"> - <div class="panel-heading"> - <div class="title"> - {{$t('settings.style.preview.header')}} - <span class="badge badge-notification"> - 99 - </span> - </div> - <span class="faint"> - {{$t('settings.style.preview.header_faint')}} - </span> - <span class="alert error"> - {{$t('settings.style.preview.error')}} - </span> - <button class="btn"> - {{$t('settings.style.preview.button')}} - </button> - </div> - <div class="panel-body theme-preview-content"> - <div class="post"> - <div class="avatar"> - ( ͡° ͜ʖ ͡°) - </div> - <div class="content"> - <h4> - {{$t('settings.style.preview.content')}} - </h4> - - <i18n path="settings.style.preview.text"> - <code style="font-family: var(--postCodeFont)"> - {{$t('settings.style.preview.mono')}} - </code> - <a style="color: var(--link)"> - {{$t('settings.style.preview.link')}} - </a> - </i18n> - - <div class="icons"> - <i style="color: var(--cBlue)" class="icon-reply"/> - <i style="color: var(--cGreen)" class="icon-retweet"/> - <i style="color: var(--cOrange)" class="icon-star"/> - <i style="color: var(--cRed)" class="icon-cancel"/> - </div> - </div> - </div> - - <div class="after-post"> - <div class="avatar-alt"> - :^) - </div> - <div class="content"> - <i18n path="settings.style.preview.fine_print" tag="span" class="faint"> - <a style="color: var(--faintLink)"> - {{$t('settings.style.preview.faint_link')}} - </a> - </i18n> - </div> - </div> - <div class="separator"></div> - - <span class="alert error"> - {{$t('settings.style.preview.error')}} - </span> - <input :value="$t('settings.style.preview.input')" type="text"> - - <div class="actions"> - <span class="checkbox"> - <input checked="very yes" type="checkbox" id="preview_checkbox"> - <label for="preview_checkbox">{{$t('settings.style.preview.checkbox')}}</label> - </span> - <button class="btn"> - {{$t('settings.style.preview.button')}} - </button> - </div> - </div> - </div> + <preview :style="previewRules"/> </div> <keep-alive> @@ -235,6 +163,7 @@ <OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/> </div> </div> + <div :label="$t('settings.style.radii._tab_label')" class="radius-container"> <div class="tab-header"> <p>{{$t('settings.radii_help')}}</p> @@ -249,6 +178,7 @@ <RangeInput name="attachmentRadius" :label="$t('settings.attachmentRadius')" v-model="attachmentRadiusLocal" :fallback="previewTheme.radii.attachment" max="50" hardMin="0"/> <RangeInput name="tooltipRadius" :label="$t('settings.tooltipRadius')" v-model="tooltipRadiusLocal" :fallback="previewTheme.radii.tooltip" max="50" hardMin="0"/> </div> + <div :label="$t('settings.style.shadows._tab_label')" class="shadow-container"> <div class="tab-header shadow-selector"> <div class="select-container"> @@ -294,6 +224,7 @@ <p>{{$t('settings.style.shadows.filter_hint.spread_zero')}}</p> </div> </div> + <div :label="$t('settings.style.fonts._tab_label')" class="fonts-container"> <div class="tab-header"> <p>{{$t('settings.style.fonts.help')}}</p> diff --git a/src/modules/config.js b/src/modules/config.js index fb9b3ca6..45ac8f65 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,5 +1,5 @@ import { set, delete as del } from 'vue' -import { setPreset, setColors } from '../services/style_setter/style_setter.js' +import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' const browserLocale = (window.navigator.language || 'en').split('-')[0] @@ -57,7 +57,7 @@ const config = { setPreset(value, commit) break case 'customTheme': - setColors(value, commit) + applyTheme(value, commit) } } } |
