From 8a590f9269086ac737869e948954c74d6317541e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 8 Mar 2021 19:18:43 +0200 Subject: [PATCH 1/9] Cleaned up panel-footer. No longer uses header styles since those look and work ugly. --- src/App.scss | 19 +++++++++++-------- .../notifications/notifications.vue | 4 ++-- src/components/timeline/timeline.vue | 8 ++++---- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/App.scss b/src/App.scss index 90d083bbba..a3b2a08e47 100644 --- a/src/App.scss +++ b/src/App.scss @@ -548,8 +548,17 @@ main-router { } .panel-footer { + display: flex; border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + flex: none; + padding: 0.6em 0.6em; + text-align: left; + line-height: 28px; + align-items: baseline; + border-width: 1px 0 0 0; + border-style: solid; + border-color: var(--border, $fallback--border); .faint { color: $fallback--faint; @@ -862,16 +871,10 @@ nav { } .new-status-notification { - position:relative; - margin-top: -1px; + position: relative; font-size: 1.1em; - border-width: 1px 0 0 0; - border-style: solid; - border-color: var(--border, $fallback--border); - padding: 10px; z-index: 1; - background-color: $fallback--fg; - background-color: var(--panel, $fallback--fg); + flex: 1; } .chat-layout { diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index 725d1ad4ed..eb951f1842 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -37,7 +37,7 @@
diff --git a/src/services/export_import/export_import.js b/src/services/export_import/export_import.js new file mode 100644 index 0000000000..0bf9eadc49 --- /dev/null +++ b/src/services/export_import/export_import.js @@ -0,0 +1,57 @@ +export const newExporter = ({ + filename = 'data', + getExportedObject +}) => ({ + exportData () { + const stringified = JSON.stringify(getExportedObject(), 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', `${filename}.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) + } +}) + +export const newImporter = ({ + onImport, + onImportFailure, + validator = () => true +}) => ({ + importData () { + const filePicker = document.createElement('input') + filePicker.setAttribute('type', 'file') + filePicker.setAttribute('accept', '.json') + console.log(1) + + 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 validationResult = validator(parsed) + if (validationResult === true) { + onImport(parsed) + } else { + onImportFailure({ validationResult }) + } + } catch (error) { + onImportFailure({ error }) + } + } + console.log(2) + reader.readAsText(event.target.files[0]) + } + }) + + document.body.appendChild(filePicker) + filePicker.click() + document.body.removeChild(filePicker) + } +}) From dda95543e82ff21ca03c96f3366735a9ec43c0e6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 8 Mar 2021 19:53:30 +0200 Subject: [PATCH 4/9] implemented import/export for themes --- .../settings_modal/settings_modal.js | 80 +++++++++++++++++++ .../settings_modal/settings_modal.vue | 70 +++++++++++++++- src/i18n/en.json | 5 ++ src/modules/config.js | 5 ++ 4 files changed, 158 insertions(+), 2 deletions(-) diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js index f0d49c9158..7f6ccccbda 100644 --- a/src/components/settings_modal/settings_modal.js +++ b/src/components/settings_modal/settings_modal.js @@ -2,10 +2,52 @@ import Modal from 'src/components/modal/modal.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' +import Popover from '../popover/popover.vue' +import { library } from '@fortawesome/fontawesome-svg-core' +import { cloneDeep } from 'lodash' +import { + newImporter, + newExporter +} from 'src/services/export_import/export_import.js' +import { + faTimes, + faFileUpload, + faFileDownload, + faChevronDown +} from '@fortawesome/free-solid-svg-icons' +import { + faWindowMinimize +} from '@fortawesome/free-regular-svg-icons' + +library.add( + faTimes, + faWindowMinimize, + faFileUpload, + faFileDownload, + faChevronDown +) const SettingsModal = { + data () { + return { + dataImporter: newImporter({ + validator: this.importValidator, + onImport: this.onImport, + onImportFailure: this.onImportFailure + }), + dataThemeExporter: newExporter({ + filename: 'pleromafe_settings.full', + getExportedObject: () => this.generateExport(true) + }), + dataExporter: newExporter({ + filename: 'pleromafe_settings', + getExportedObject: () => this.generateExport() + }) + } + }, components: { Modal, + Popover, SettingsModalContent: getResettableAsyncComponent( () => import('./settings_modal_content.vue'), { @@ -21,6 +63,44 @@ const SettingsModal = { }, peekModal () { this.$store.dispatch('togglePeekSettingsModal') + }, + importValidator (data) { + return data._pleroma_settings_version[0] === 1 + }, + onImportFailure () { + this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_settings_imported', level: 'error' }) + }, + onImport (data) { + this.$store.dispatch('loadSettings', data) + }, + restore () { + console.log(this.dataImporter) + this.dataImporter.importData() + }, + backup () { + this.dataExporter.exportData() + }, + backupWithTheme () { + this.dataThemeExporter.exportData() + }, + generateExport (theme = false) { + const { config } = this.$store.state + let sample = config + if (!theme) { + const ignoreList = new Set([ + 'customTheme', + 'customThemeSource', + 'colors' + ]) + sample = Object.fromEntries( + Object + .entries(sample) + .filter(([key]) => !ignoreList.has(key)) + ) + } + const clone = cloneDeep(sample) + clone._pleroma_settings_version = [1, 0] + return clone } }, computed: { diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue index 552ca41f94..a142bcd146 100644 --- a/src/components/settings_modal/settings_modal.vue +++ b/src/components/settings_modal/settings_modal.vue @@ -32,19 +32,85 @@
+ diff --git a/src/i18n/en.json b/src/i18n/en.json index 2aefebc9d3..049d3d11c3 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -357,6 +357,7 @@ "interface": "Interface", "interfaceLanguage": "Interface language", "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.", + "invalid_settings_imported": "The selected file is not a supported Pleroma settings backup. No changes were made.", "limited_availability": "Unavailable in your browser", "links": "Links", "lock_account_description": "Restrict your account to approved followers only", @@ -364,6 +365,10 @@ "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", "mutes_tab": "Mutes", "play_videos_in_modal": "Play videos in a popup frame", + "backup_restore": "Settings backup", + "backup_settings": "Backup settings to file", + "backup_settings_theme": "Backup settings and theme to file", + "restore_settings": "Restore settings from file", "profile_fields": { "label": "Profile metadata", "add_field": "Add Field", diff --git a/src/modules/config.js b/src/modules/config.js index eca58c12c3..ec3b4c43a8 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -110,6 +110,11 @@ const config = { } }, actions: { + loadSettings ({ dispatch }, data) { + Object.keys(this.state.config).forEach( + name => dispatch('setOption', { name, value: data[name] }) + ) + }, setHighlight ({ commit, dispatch }, { user, color, type }) { commit('setHighlight', { user, color, type }) }, From 395e12cbc6e22459508023daabba87b62a65050e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 8 Mar 2021 21:00:43 +0200 Subject: [PATCH 5/9] better error/warnings --- .../settings_modal/settings_modal.js | 54 +++++++++++++++++-- .../settings_modal/settings_modal.vue | 8 +-- src/i18n/en.json | 17 ++++-- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js index 7f6ccccbda..f88184caba 100644 --- a/src/components/settings_modal/settings_modal.js +++ b/src/components/settings_modal/settings_modal.js @@ -19,6 +19,9 @@ import { faWindowMinimize } from '@fortawesome/free-regular-svg-icons' +const PLEROMAFE_SETTINGS_MAJOR_VERSION = 1 +const PLEROMAFE_SETTINGS_MINOR_VERSION = 0 + library.add( faTimes, faWindowMinimize, @@ -65,12 +68,52 @@ const SettingsModal = { this.$store.dispatch('togglePeekSettingsModal') }, importValidator (data) { - return data._pleroma_settings_version[0] === 1 + if (!Array.isArray(data._pleroma_settings_version)) { + return { + messageKey: 'settings.file_import_export.invalid_file' + } + } + + const [major, minor] = data._pleroma_settings_version + + if (major > PLEROMAFE_SETTINGS_MAJOR_VERSION) { + return { + messageKey: 'settings.file_export_import.errors.file_too_new', + messageArgs: { + fileMajor: major, + feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION + } + } + } + + if (major < PLEROMAFE_SETTINGS_MAJOR_VERSION) { + return { + messageKey: 'settings.file_export_import.errors.file_too_old', + messageArgs: { + fileMajor: major, + feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION + } + } + } + + if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) { + this.$store.dispatch('pushGlobalNotice', { + level: 'warning', + messageKey: 'settings.file_export_import.errors.file_slightly_new', + }) + } + + return true }, - onImportFailure () { - this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_settings_imported', level: 'error' }) + onImportFailure (result) { + if (result.error) { + this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_settings_imported', level: 'error' }) + } else { + this.$store.dispatch('pushGlobalNotice', { ...result.validationResult, level: 'error' }) + } }, onImport (data) { + if (data) this.$store.dispatch('loadSettings', data) }, restore () { @@ -99,7 +142,10 @@ const SettingsModal = { ) } const clone = cloneDeep(sample) - clone._pleroma_settings_version = [1, 0] + clone._pleroma_settings_version = [ + PLEROMAFE_SETTINGS_MAJOR_VERSION, + PLEROMAFE_SETTINGS_MINOR_VERSION + ] return clone } }, diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue index a142bcd146..c37608e2ca 100644 --- a/src/components/settings_modal/settings_modal.vue +++ b/src/components/settings_modal/settings_modal.vue @@ -67,7 +67,7 @@ class="btn button-default" :title="$t('general.close')" > - {{ $t("settings.backup_restore") }} + {{ $t("settings.file_export_import.backup_restore") }} @@ -85,7 +85,7 @@ {{ $t("settings.backup_settings") }} + />{{ $t("settings.file_export_import.backup_settings") }} diff --git a/src/i18n/en.json b/src/i18n/en.json index 049d3d11c3..1c7fe94317 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -357,7 +357,6 @@ "interface": "Interface", "interfaceLanguage": "Interface language", "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.", - "invalid_settings_imported": "The selected file is not a supported Pleroma settings backup. No changes were made.", "limited_availability": "Unavailable in your browser", "links": "Links", "lock_account_description": "Restrict your account to approved followers only", @@ -365,10 +364,18 @@ "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", "mutes_tab": "Mutes", "play_videos_in_modal": "Play videos in a popup frame", - "backup_restore": "Settings backup", - "backup_settings": "Backup settings to file", - "backup_settings_theme": "Backup settings and theme to file", - "restore_settings": "Restore settings from file", + "file_export_import": { + "backup_restore": "Settings backup", + "backup_settings": "Backup settings to file", + "backup_settings_theme": "Backup settings and theme to file", + "restore_settings": "Restore settings from file", + "errors": { + "invalid_file": "The selected file is not a supported Pleroma settings backup. No changes were made.", + "file_too_new": "Incompatile major version: {fileMajor}, this PleromaFE (settings ver {feMajor}) is too old to handle it", + "file_too_old": "Incompatile major version: {fileMajor}, file version is too old and not supported (min. set. ver. {feMajor})", + "file_slightly_new": "File minor version is different, some settings might not load" + } + }, "profile_fields": { "label": "Profile metadata", "add_field": "Add Field", From 914b4eb5938f00284afff3c210dcafa94e48f510 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 8 Mar 2021 21:03:55 +0200 Subject: [PATCH 6/9] lint --- src/components/settings_modal/settings_modal.js | 5 ++--- src/components/settings_modal/settings_modal.vue | 8 ++++---- .../settings_modal/tabs/theme_tab/theme_tab.vue | 10 ++++++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js index f88184caba..7010e4aaa2 100644 --- a/src/components/settings_modal/settings_modal.js +++ b/src/components/settings_modal/settings_modal.js @@ -99,7 +99,7 @@ const SettingsModal = { if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) { this.$store.dispatch('pushGlobalNotice', { level: 'warning', - messageKey: 'settings.file_export_import.errors.file_slightly_new', + messageKey: 'settings.file_export_import.errors.file_slightly_new' }) } @@ -113,8 +113,7 @@ const SettingsModal = { } }, onImport (data) { - if (data) - this.$store.dispatch('loadSettings', data) + if (data) { this.$store.dispatch('loadSettings', data) } }, restore () { console.log(this.dataImporter) diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue index c37608e2ca..c7da543358 100644 --- a/src/components/settings_modal/settings_modal.vue +++ b/src/components/settings_modal/settings_modal.vue @@ -31,8 +31,8 @@ - From fada49768dcd12faae4f7a188bdd36ea60f8b0a8 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 8 Mar 2021 21:14:03 +0200 Subject: [PATCH 7/9] extra protection to not write what we don't know --- src/modules/config.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/modules/config.js b/src/modules/config.js index ec3b4c43a8..352adf2014 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -111,7 +111,16 @@ const config = { }, actions: { loadSettings ({ dispatch }, data) { - Object.keys(this.state.config).forEach( + const knownKeys = new Set(Object.keys(this.state.config)) + const presentKeys = new Set(Object.keys(data)) + const intersection = new Set() + for (let elem of presentKeys) { + if (knownKeys.has(elem)) { + intersection.add(elem) + } + } + + Object.keys(intersection).forEach( name => dispatch('setOption', { name, value: data[name] }) ) }, From 3d95ea6acbd47cabee519b2450647512ac2d5b46 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 8 Mar 2021 21:56:20 +0200 Subject: [PATCH 8/9] cleanup + fix --- src/components/settings_modal/settings_modal.js | 1 - src/modules/config.js | 4 ++-- src/services/export_import/export_import.js | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js index 7010e4aaa2..0404348334 100644 --- a/src/components/settings_modal/settings_modal.js +++ b/src/components/settings_modal/settings_modal.js @@ -116,7 +116,6 @@ const SettingsModal = { if (data) { this.$store.dispatch('loadSettings', data) } }, restore () { - console.log(this.dataImporter) this.dataImporter.importData() }, backup () { diff --git a/src/modules/config.js b/src/modules/config.js index 352adf2014..b0fc98f56c 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -111,7 +111,7 @@ const config = { }, actions: { loadSettings ({ dispatch }, data) { - const knownKeys = new Set(Object.keys(this.state.config)) + const knownKeys = new Set(Object.keys(defaultState)) const presentKeys = new Set(Object.keys(data)) const intersection = new Set() for (let elem of presentKeys) { @@ -120,7 +120,7 @@ const config = { } } - Object.keys(intersection).forEach( + intersection.forEach( name => dispatch('setOption', { name, value: data[name] }) ) }, diff --git a/src/services/export_import/export_import.js b/src/services/export_import/export_import.js index 0bf9eadc49..ac67cf9c52 100644 --- a/src/services/export_import/export_import.js +++ b/src/services/export_import/export_import.js @@ -26,7 +26,6 @@ export const newImporter = ({ const filePicker = document.createElement('input') filePicker.setAttribute('type', 'file') filePicker.setAttribute('accept', '.json') - console.log(1) filePicker.addEventListener('change', event => { if (event.target.files[0]) { @@ -45,7 +44,6 @@ export const newImporter = ({ onImportFailure({ error }) } } - console.log(2) reader.readAsText(event.target.files[0]) } }) From 5c064ccf553ffcd3286ad1b3305633c5c4c4c0c4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 11 Mar 2021 15:14:30 +0200 Subject: [PATCH 9/9] fix some css problems + tiny bit cleaning up --- src/App.scss | 5 ++- .../notifications/notifications.scss | 7 +++-- .../notifications/notifications.vue | 6 ++-- src/components/timeline/timeline.scss | 31 +++++++++++++++++++ src/components/timeline/timeline.vue | 30 +----------------- 5 files changed, 44 insertions(+), 35 deletions(-) create mode 100644 src/components/timeline/timeline.scss diff --git a/src/App.scss b/src/App.scss index a3b2a08e47..da0cfca7a6 100644 --- a/src/App.scss +++ b/src/App.scss @@ -547,7 +547,10 @@ main-router { border-radius: var(--panelRadius, $fallback--panelRadius); } -.panel-footer { +/* TODO Should remove timeline-footer from here when we refactor panels into + * separate component and utilize slots + */ +.panel-footer, .timeline-footer { display: flex; border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 682ae127d1..2bb627a8c9 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -1,6 +1,6 @@ @import '../../_variables.scss'; -.notifications { +.Notifications { &:not(.minimal) { // a bit of a hack to allow scrolling below notifications padding-bottom: 15em; @@ -11,6 +11,10 @@ color: var(--text, $fallback--text); } + .notifications-footer { + border: none; + } + .notification { position: relative; @@ -82,7 +86,6 @@ } } - .follow-text, .move-text { padding: 0.5em 0; overflow-wrap: break-word; diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index eb951f1842..dc663ce245 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -1,7 +1,7 @@