commit: 9982376f9a316a5e4224ea44c7392b272f5549d1
parent: 65dfceae59de8c0a8d79168f345d16942695c573
Author: kaniini <nenolod@gmail.com>
Date:   Mon, 20 Aug 2018 00:03:35 +0000
Merge branch 'feature/theme-export-import' into 'develop'
Theme import & export
Closes #119
See merge request pleroma/pleroma-fe!290
Diffstat:
7 files changed, 92 insertions(+), 28 deletions(-)
diff --git a/src/App.scss b/src/App.scss
@@ -63,6 +63,8 @@ button{
   box-shadow: 0px 0px 2px black;
   font-size: 14px;
   font-family: sans-serif;
+  min-width: 10em;
+  min-height: 2em;
 
   &:hover {
     box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3);
diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue
@@ -34,11 +34,6 @@
 @import '../../_variables.scss';
 
 .login-form {
-  .btn {
-    min-height: 28px;
-    width: 10em;
-  }
-
   .error {
     text-align: center;
   }
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
@@ -107,10 +107,6 @@
     padding: 0.5em;
     height: 32px;
 
-    button {
-      width: 10em;
-    }
-
     p {
       margin: 0.35em;
       padding: 0.35em;
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
@@ -113,8 +113,6 @@
 
   .btn {
     margin-top: 1em;
-    min-height: 28px;
-    width: 10em;
   }
 }
 .setting-list {
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
@@ -5,6 +5,7 @@ export default {
     return {
       availableStyles: [],
       selected: this.$store.state.config.theme,
+      invalidThemeImported: false,
       bgColorLocal: '',
       btnColorLocal: '',
       textColorLocal: '',
@@ -32,25 +33,61 @@ export default {
       })
   },
   mounted () {
-    this.bgColorLocal = rgbstr2hex(this.$store.state.config.colors.bg)
-    this.btnColorLocal = rgbstr2hex(this.$store.state.config.colors.btn)
-    this.textColorLocal = rgbstr2hex(this.$store.state.config.colors.fg)
-    this.linkColorLocal = rgbstr2hex(this.$store.state.config.colors.link)
-
-    this.redColorLocal = rgbstr2hex(this.$store.state.config.colors.cRed)
-    this.blueColorLocal = rgbstr2hex(this.$store.state.config.colors.cBlue)
-    this.greenColorLocal = rgbstr2hex(this.$store.state.config.colors.cGreen)
-    this.orangeColorLocal = rgbstr2hex(this.$store.state.config.colors.cOrange)
-
-    this.btnRadiusLocal = this.$store.state.config.radii.btnRadius || 4
-    this.inputRadiusLocal = this.$store.state.config.radii.inputRadius || 4
-    this.panelRadiusLocal = this.$store.state.config.radii.panelRadius || 10
-    this.avatarRadiusLocal = this.$store.state.config.radii.avatarRadius || 5
-    this.avatarAltRadiusLocal = this.$store.state.config.radii.avatarAltRadius || 50
-    this.tooltipRadiusLocal = this.$store.state.config.radii.tooltipRadius || 2
-    this.attachmentRadiusLocal = this.$store.state.config.radii.attachmentRadius || 5
+    this.normalizeLocalState(this.$store.state.config.colors, this.$store.state.config.radii)
   },
   methods: {
+    exportCurrentTheme () {
+      const stringified = JSON.stringify({
+        // To separate from other random JSON files and possible future theme formats
+        _pleroma_theme_version: 1,
+        colors: this.$store.state.config.colors,
+        radii: this.$store.state.config.radii
+      }, 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.colors, parsed.radii)
+              } 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)
+    },
+
     setCustomTheme () {
       if (!this.bgColorLocal && !this.btnColorLocal && !this.linkColorLocal) {
         // reset to picked themes
@@ -95,6 +132,26 @@ export default {
             attachmentRadius: this.attachmentRadiusLocal
           }})
       }
+    },
+
+    normalizeLocalState (colors, radii) {
+      this.bgColorLocal = rgbstr2hex(colors.bg)
+      this.btnColorLocal = rgbstr2hex(colors.btn)
+      this.textColorLocal = rgbstr2hex(colors.fg)
+      this.linkColorLocal = rgbstr2hex(colors.link)
+
+      this.redColorLocal = rgbstr2hex(colors.cRed)
+      this.blueColorLocal = rgbstr2hex(colors.cBlue)
+      this.greenColorLocal = rgbstr2hex(colors.cGreen)
+      this.orangeColorLocal = rgbstr2hex(colors.cOrange)
+
+      this.btnRadiusLocal = radii.btnRadius || 4
+      this.inputRadiusLocal = radii.inputRadius || 4
+      this.panelRadiusLocal = radii.panelRadius || 10
+      this.avatarRadiusLocal = radii.avatarRadius || 5
+      this.avatarAltRadiusLocal = radii.avatarAltRadius || 50
+      this.tooltipRadiusLocal = radii.tooltipRadius || 2
+      this.attachmentRadiusLocal = radii.attachmentRadius || 5
     }
   },
   watch: {
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
@@ -11,6 +11,11 @@
         <i class="icon-down-open"/>
       </label>
     </div>
+    <div>
+      <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>
     <div class="color-container">
       <p>{{$t('settings.theme_help')}}</p>
       <div class="color-item">
@@ -134,6 +139,11 @@
   margin-right: 1em;
 }
 
+.import-warning {
+  color: $fallback--cRed;
+  color: var(--cRed, $fallback--cRed);
+}
+
 .radius-container,
 .color-container {
   display: flex;
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
@@ -48,6 +48,9 @@ const de = {
     settings: 'Einstellungen',
     theme: 'Farbschema',
     presets: 'Voreinstellungen',
+    export_theme: 'Aktuelles Theme exportieren',
+    import_theme: 'Gespeichertes Theme laden',
+    invalid_theme_imported: 'Die ausgewählte Datei ist kein unterstütztes Pleroma-Theme. Keine Änderungen wurden vorgenommen.',
     theme_help: 'Benutze HTML Farbcodes (#rrggbb) um dein Farbschema anzupassen',
     radii_help: 'Kantenrundung (in Pixel) der Oberfläche anpassen',
     background: 'Hintergrund',
@@ -288,7 +291,10 @@ const en = {
     settings: 'Settings',
     theme: 'Theme',
     presets: 'Presets',
+    export_theme: 'Export current theme',
+    import_theme: 'Load saved theme',
     theme_help: 'Use hex color codes (#rrggbb) to customize your color theme.',
+    invalid_theme_imported: 'The selected file is not a supported Pleroma theme. No changes to your theme were made.',
     radii_help: 'Set up interface edge rounding (in pixels)',
     background: 'Background',
     foreground: 'Foreground',