From dda95543e82ff21ca03c96f3366735a9ec43c0e6 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 8 Mar 2021 19:53:30 +0200
Subject: [PATCH] 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 f0d49c91..7f6ccccb 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 552ca41f..a142bcd1 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -32,19 +32,85 @@
         <button
           class="btn button-default"
           @click="peekModal"
+          :title="$t('general.peek')"
         >
-          {{ $t('general.peek') }}
+          <FAIcon
+            :icon="['far', 'window-minimize']"
+            fixed-width
+          />
         </button>
         <button
           class="btn button-default"
           @click="closeModal"
+          :title="$t('general.close')"
         >
-          {{ $t('general.close') }}
+          <FAIcon
+            icon="times"
+            fixed-width
+          />
         </button>
       </div>
       <div class="panel-body">
         <SettingsModalContent v-if="modalOpenedOnce" />
       </div>
+      <div class="panel-footer">
+        <Popover
+          class="export"
+          trigger="click"
+          placement="top"
+          :offset="{ y: 5, x: 5 }"
+          :bound-to="{ x: 'container' }"
+          remove-padding
+          >
+          <button
+            slot="trigger"
+            class="btn button-default"
+            :title="$t('general.close')"
+            >
+            <span>{{ $t("settings.backup_restore") }}</span>
+            <FAIcon
+              icon="chevron-down"
+            />
+          </button>
+          <div
+            slot="content"
+            slot-scope="{close}"
+          >
+            <div class="dropdown-menu">
+              <button
+                class="button-default dropdown-item dropdown-item-icon"
+                @click.prevent="backup"
+                @click="close"
+              >
+                <FAIcon
+                  icon="file-download"
+                  fixed-width
+                /><span>{{ $t("settings.backup_settings") }}</span>
+              </button>
+              <button
+                class="button-default dropdown-item dropdown-item-icon"
+                @click.prevent="backupWithTheme"
+                @click="close"
+              >
+                <FAIcon
+                  icon="file-download"
+                  fixed-width
+                /><span>{{ $t("settings.backup_settings_theme") }}</span>
+              </button>
+              <button
+                class="button-default dropdown-item dropdown-item-icon"
+                @click.prevent="restore"
+                @click="close"
+              >
+                <FAIcon
+                  icon="file-upload"
+                  fixed-width
+                /><span>{{ $t("settings.restore_settings") }}</span>
+              </button>
+            </div>
+          </div>
+        </Popover>
+      </div>
     </div>
   </Modal>
 </template>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 2aefebc9..049d3d11 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 eca58c12..ec3b4c43 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 })
     },