forked from AkkomaGang/akkoma-fe
separated preview and exported from style_switcher
This commit is contained in:
parent
a17ac74df7
commit
51dccb7887
5 changed files with 200 additions and 147 deletions
75
src/components/export_import/export_import.vue
Normal file
75
src/components/export_import/export_import.vue
Normal file
|
@ -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>
|
78
src/components/style_switcher/preview.vue
Normal file
78
src/components/style_switcher/preview.vue
Normal file
|
@ -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>
|
|
@ -8,6 +8,8 @@ import ShadowControl from '../shadow_control/shadow_control.vue'
|
||||||
import FontControl from '../font_control/font_control.vue'
|
import FontControl from '../font_control/font_control.vue'
|
||||||
import ContrastRatio from '../contrast_ratio/contrast_ratio.vue'
|
import ContrastRatio from '../contrast_ratio/contrast_ratio.vue'
|
||||||
import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
|
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
|
// List of color values used in v1
|
||||||
const v1OnlyNames = [
|
const v1OnlyNames = [
|
||||||
|
@ -26,7 +28,6 @@ export default {
|
||||||
return {
|
return {
|
||||||
availableStyles: [],
|
availableStyles: [],
|
||||||
selected: this.$store.state.config.theme,
|
selected: this.$store.state.config.theme,
|
||||||
invalidThemeImported: false,
|
|
||||||
|
|
||||||
previewShadows: {},
|
previewShadows: {},
|
||||||
previewColors: {},
|
previewColors: {},
|
||||||
|
@ -293,20 +294,11 @@ export default {
|
||||||
},
|
},
|
||||||
themeValid () {
|
themeValid () {
|
||||||
return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid
|
return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid
|
||||||
}
|
|
||||||
},
|
},
|
||||||
components: {
|
exportedTheme () {
|
||||||
ColorInput,
|
|
||||||
OpacityInput,
|
|
||||||
RangeInput,
|
|
||||||
ContrastRatio,
|
|
||||||
ShadowControl,
|
|
||||||
FontControl,
|
|
||||||
TabSwitcher
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
exportCurrentTheme () {
|
|
||||||
const saveEverything = !this.keepFonts && !this.keepShadows && !this.keepColors && !this.keepOpacity && !this.keepRoundness
|
const saveEverything = !this.keepFonts && !this.keepShadows && !this.keepColors && !this.keepOpacity && !this.keepRoundness
|
||||||
|
|
||||||
|
// TODO change into delete-less version.
|
||||||
const theme = {
|
const theme = {
|
||||||
shadows: this.shadowsLocal,
|
shadows: this.shadowsLocal,
|
||||||
fonts: this.fontsLocal,
|
fonts: this.fontsLocal,
|
||||||
|
@ -331,57 +323,24 @@ export default {
|
||||||
delete theme.radii
|
delete theme.radii
|
||||||
}
|
}
|
||||||
|
|
||||||
const stringified = JSON.stringify({
|
return {
|
||||||
// To separate from other random JSON files and possible future theme formats
|
// To separate from other random JSON files and possible future theme formats
|
||||||
_pleroma_theme_version: 2, theme
|
_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)
|
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
importTheme () {
|
ColorInput,
|
||||||
this.invalidThemeImported = false
|
OpacityInput,
|
||||||
const filePicker = document.createElement('input')
|
RangeInput,
|
||||||
filePicker.setAttribute('type', 'file')
|
ContrastRatio,
|
||||||
filePicker.setAttribute('accept', '.json')
|
ShadowControl,
|
||||||
|
FontControl,
|
||||||
filePicker.addEventListener('change', event => {
|
TabSwitcher,
|
||||||
if (event.target.files[0]) {
|
Preview,
|
||||||
// eslint-disable-next-line no-undef
|
ExportImport
|
||||||
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)
|
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
setCustomTheme () {
|
setCustomTheme () {
|
||||||
this.$store.dispatch('setOption', {
|
this.$store.dispatch('setOption', {
|
||||||
name: 'customTheme',
|
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 () {
|
clearAll () {
|
||||||
const state = this.$store.state.config.customTheme
|
const state = this.$store.state.config.customTheme
|
||||||
const version = state.colors ? 2 : 'l1'
|
const version = state.colors ? 2 : 'l1'
|
||||||
|
|
|
@ -18,11 +18,14 @@
|
||||||
<i class="icon-down-open"/>
|
<i class="icon-down-open"/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="import-export">
|
<export-import
|
||||||
<button class="btn" @click="exportCurrentTheme">{{ $t('settings.export_theme') }}</button>
|
:exportObject='exportedTheme'
|
||||||
<button class="btn" @click="importTheme">{{ $t('settings.import_theme') }}</button>
|
:exportLabel='$t("settings.export_theme")'
|
||||||
<p v-if="invalidThemeImported" class="import-warning">{{ $t('settings.invalid_theme_imported') }}</p>
|
:importLabel='$t("settings.import_theme")'
|
||||||
</div>
|
:importFailedText='$t("settings.invalid_theme_imported")'
|
||||||
|
:onImport='onImport'
|
||||||
|
:validator='importValidator'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="save-load-options">
|
<div class="save-load-options">
|
||||||
<span>
|
<span>
|
||||||
|
@ -58,82 +61,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview-container">
|
<div class="preview-container">
|
||||||
<div class="panel dummy" :style="previewRules">
|
<preview :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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
|
@ -235,6 +163,7 @@
|
||||||
<OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/>
|
<OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :label="$t('settings.style.radii._tab_label')" class="radius-container">
|
<div :label="$t('settings.style.radii._tab_label')" class="radius-container">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<p>{{$t('settings.radii_help')}}</p>
|
<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="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"/>
|
<RangeInput name="tooltipRadius" :label="$t('settings.tooltipRadius')" v-model="tooltipRadiusLocal" :fallback="previewTheme.radii.tooltip" max="50" hardMin="0"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :label="$t('settings.style.shadows._tab_label')" class="shadow-container">
|
<div :label="$t('settings.style.shadows._tab_label')" class="shadow-container">
|
||||||
<div class="tab-header shadow-selector">
|
<div class="tab-header shadow-selector">
|
||||||
<div class="select-container">
|
<div class="select-container">
|
||||||
|
@ -294,6 +224,7 @@
|
||||||
<p>{{$t('settings.style.shadows.filter_hint.spread_zero')}}</p>
|
<p>{{$t('settings.style.shadows.filter_hint.spread_zero')}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :label="$t('settings.style.fonts._tab_label')" class="fonts-container">
|
<div :label="$t('settings.style.fonts._tab_label')" class="fonts-container">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<p>{{$t('settings.style.fonts.help')}}</p>
|
<p>{{$t('settings.style.fonts.help')}}</p>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { set, delete as del } from 'vue'
|
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]
|
const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ const config = {
|
||||||
setPreset(value, commit)
|
setPreset(value, commit)
|
||||||
break
|
break
|
||||||
case 'customTheme':
|
case 'customTheme':
|
||||||
setColors(value, commit)
|
applyTheme(value, commit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue