logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe

style_switcher.js (22319B)


      1 import { set, delete as del } from 'vue'
      2 import {
      3   rgb2hex,
      4   hex2rgb,
      5   getContrastRatioLayers
      6 } from '../../services/color_convert/color_convert.js'
      7 import {
      8   DEFAULT_SHADOWS,
      9   generateColors,
     10   generateShadows,
     11   generateRadii,
     12   generateFonts,
     13   composePreset,
     14   getThemes,
     15   shadows2to3,
     16   colors2to3
     17 } from '../../services/style_setter/style_setter.js'
     18 import {
     19   SLOT_INHERITANCE
     20 } from '../../services/theme_data/pleromafe.js'
     21 import {
     22   CURRENT_VERSION,
     23   OPACITIES,
     24   getLayers,
     25   getOpacitySlot
     26 } from '../../services/theme_data/theme_data.service.js'
     27 import ColorInput from '../color_input/color_input.vue'
     28 import RangeInput from '../range_input/range_input.vue'
     29 import OpacityInput from '../opacity_input/opacity_input.vue'
     30 import ShadowControl from '../shadow_control/shadow_control.vue'
     31 import FontControl from '../font_control/font_control.vue'
     32 import ContrastRatio from '../contrast_ratio/contrast_ratio.vue'
     33 import TabSwitcher from '../tab_switcher/tab_switcher.js'
     34 import Preview from './preview.vue'
     35 import ExportImport from '../export_import/export_import.vue'
     36 import Checkbox from '../checkbox/checkbox.vue'
     37 
     38 // List of color values used in v1
     39 const v1OnlyNames = [
     40   'bg',
     41   'fg',
     42   'text',
     43   'link',
     44   'cRed',
     45   'cGreen',
     46   'cBlue',
     47   'cOrange'
     48 ].map(_ => _ + 'ColorLocal')
     49 
     50 const colorConvert = (color) => {
     51   if (color.startsWith('--') || color === 'transparent') {
     52     return color
     53   } else {
     54     return hex2rgb(color)
     55   }
     56 }
     57 
     58 export default {
     59   data () {
     60     return {
     61       availableStyles: [],
     62       selected: this.$store.getters.mergedConfig.theme,
     63       themeWarning: undefined,
     64       tempImportFile: undefined,
     65       engineVersion: 0,
     66 
     67       previewShadows: {},
     68       previewColors: {},
     69       previewRadii: {},
     70       previewFonts: {},
     71 
     72       shadowsInvalid: true,
     73       colorsInvalid: true,
     74       radiiInvalid: true,
     75 
     76       keepColor: false,
     77       keepShadows: false,
     78       keepOpacity: false,
     79       keepRoundness: false,
     80       keepFonts: false,
     81 
     82       ...Object.keys(SLOT_INHERITANCE)
     83         .map(key => [key, ''])
     84         .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}),
     85 
     86       ...Object.keys(OPACITIES)
     87         .map(key => [key, ''])
     88         .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}),
     89 
     90       shadowSelected: undefined,
     91       shadowsLocal: {},
     92       fontsLocal: {},
     93 
     94       btnRadiusLocal: '',
     95       inputRadiusLocal: '',
     96       checkboxRadiusLocal: '',
     97       panelRadiusLocal: '',
     98       avatarRadiusLocal: '',
     99       avatarAltRadiusLocal: '',
    100       attachmentRadiusLocal: '',
    101       tooltipRadiusLocal: '',
    102       chatMessageRadiusLocal: ''
    103     }
    104   },
    105   created () {
    106     const self = this
    107 
    108     getThemes()
    109       .then((promises) => {
    110         return Promise.all(
    111           Object.entries(promises)
    112             .map(([k, v]) => v.then(res => [k, res]))
    113         )
    114       })
    115       .then(themes => themes.reduce((acc, [k, v]) => {
    116         if (v) {
    117           return {
    118             ...acc,
    119             [k]: v
    120           }
    121         } else {
    122           return acc
    123         }
    124       }, {}))
    125       .then((themesComplete) => {
    126         self.availableStyles = themesComplete
    127       })
    128   },
    129   mounted () {
    130     this.loadThemeFromLocalStorage()
    131     if (typeof this.shadowSelected === 'undefined') {
    132       this.shadowSelected = this.shadowsAvailable[0]
    133     }
    134   },
    135   computed: {
    136     themeWarningHelp () {
    137       if (!this.themeWarning) return
    138       const t = this.$t
    139       const pre = 'settings.style.switcher.help.'
    140       const {
    141         origin,
    142         themeEngineVersion,
    143         type,
    144         noActionsPossible
    145       } = this.themeWarning
    146       if (origin === 'file') {
    147         // Loaded v2 theme from file
    148         if (themeEngineVersion === 2 && type === 'wrong_version') {
    149           return t(pre + 'v2_imported')
    150         }
    151         if (themeEngineVersion > CURRENT_VERSION) {
    152           return t(pre + 'future_version_imported') + ' ' +
    153             (
    154               noActionsPossible
    155                 ? t(pre + 'snapshot_missing')
    156                 : t(pre + 'snapshot_present')
    157             )
    158         }
    159         if (themeEngineVersion < CURRENT_VERSION) {
    160           return t(pre + 'future_version_imported') + ' ' +
    161             (
    162               noActionsPossible
    163                 ? t(pre + 'snapshot_missing')
    164                 : t(pre + 'snapshot_present')
    165             )
    166         }
    167       } else if (origin === 'localStorage') {
    168         if (type === 'snapshot_source_mismatch') {
    169           return t(pre + 'snapshot_source_mismatch')
    170         }
    171         // FE upgraded from v2
    172         if (themeEngineVersion === 2) {
    173           return t(pre + 'upgraded_from_v2')
    174         }
    175         // Admin downgraded FE
    176         if (themeEngineVersion > CURRENT_VERSION) {
    177           return t(pre + 'fe_downgraded') + ' ' +
    178             (
    179               noActionsPossible
    180                 ? t(pre + 'migration_snapshot_ok')
    181                 : t(pre + 'migration_snapshot_gone')
    182             )
    183         }
    184         // Admin upgraded FE
    185         if (themeEngineVersion < CURRENT_VERSION) {
    186           return t(pre + 'fe_upgraded') + ' ' +
    187             (
    188               noActionsPossible
    189                 ? t(pre + 'migration_snapshot_ok')
    190                 : t(pre + 'migration_snapshot_gone')
    191             )
    192         }
    193       }
    194     },
    195     selectedVersion () {
    196       return Array.isArray(this.selected) ? 1 : 2
    197     },
    198     currentColors () {
    199       return Object.keys(SLOT_INHERITANCE)
    200         .map(key => [key, this[key + 'ColorLocal']])
    201         .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
    202     },
    203     currentOpacity () {
    204       return Object.keys(OPACITIES)
    205         .map(key => [key, this[key + 'OpacityLocal']])
    206         .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
    207     },
    208     currentRadii () {
    209       return {
    210         btn: this.btnRadiusLocal,
    211         input: this.inputRadiusLocal,
    212         checkbox: this.checkboxRadiusLocal,
    213         panel: this.panelRadiusLocal,
    214         avatar: this.avatarRadiusLocal,
    215         avatarAlt: this.avatarAltRadiusLocal,
    216         tooltip: this.tooltipRadiusLocal,
    217         attachment: this.attachmentRadiusLocal,
    218         chatMessage: this.chatMessageRadiusLocal
    219       }
    220     },
    221     preview () {
    222       return composePreset(this.previewColors, this.previewRadii, this.previewShadows, this.previewFonts)
    223     },
    224     previewTheme () {
    225       if (!this.preview.theme.colors) return { colors: {}, opacity: {}, radii: {}, shadows: {}, fonts: {} }
    226       return this.preview.theme
    227     },
    228     // This needs optimization maybe
    229     previewContrast () {
    230       try {
    231         if (!this.previewTheme.colors.bg) return {}
    232         const colors = this.previewTheme.colors
    233         const opacity = this.previewTheme.opacity
    234         if (!colors.bg) return {}
    235         const hints = (ratio) => ({
    236           text: ratio.toPrecision(3) + ':1',
    237           // AA level, AAA level
    238           aa: ratio >= 4.5,
    239           aaa: ratio >= 7,
    240           // same but for 18pt+ texts
    241           laa: ratio >= 3,
    242           laaa: ratio >= 4.5
    243         })
    244         const colorsConverted = Object.entries(colors).reduce((acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }), {})
    245 
    246         const ratios = Object.entries(SLOT_INHERITANCE).reduce((acc, [key, value]) => {
    247           const slotIsBaseText = key === 'text' || key === 'link'
    248           const slotIsText = slotIsBaseText || (
    249             typeof value === 'object' && value !== null && value.textColor
    250           )
    251           if (!slotIsText) return acc
    252           const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
    253           const background = variant || layer
    254           const opacitySlot = getOpacitySlot(background)
    255           const textColors = [
    256             key,
    257             ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])
    258           ]
    259 
    260           const layers = getLayers(
    261             layer,
    262             variant || layer,
    263             opacitySlot,
    264             colorsConverted,
    265             opacity
    266           )
    267 
    268           return {
    269             ...acc,
    270             ...textColors.reduce((acc, textColorKey) => {
    271               const newKey = slotIsBaseText
    272                 ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
    273                 : textColorKey
    274               return {
    275                 ...acc,
    276                 [newKey]: getContrastRatioLayers(
    277                   colorsConverted[textColorKey],
    278                   layers,
    279                   colorsConverted[textColorKey]
    280                 )
    281               }
    282             }, {})
    283           }
    284         }, {})
    285 
    286         return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
    287       } catch (e) {
    288         console.warn('Failure computing contrasts', e)
    289       }
    290     },
    291     previewRules () {
    292       if (!this.preview.rules) return ''
    293       return [
    294         ...Object.values(this.preview.rules),
    295         'color: var(--text)',
    296         'font-family: var(--interfaceFont, sans-serif)'
    297       ].join(';')
    298     },
    299     shadowsAvailable () {
    300       return Object.keys(DEFAULT_SHADOWS).sort()
    301     },
    302     currentShadowOverriden: {
    303       get () {
    304         return !!this.currentShadow
    305       },
    306       set (val) {
    307         if (val) {
    308           set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _)))
    309         } else {
    310           del(this.shadowsLocal, this.shadowSelected)
    311         }
    312       }
    313     },
    314     currentShadowFallback () {
    315       return (this.previewTheme.shadows || {})[this.shadowSelected]
    316     },
    317     currentShadow: {
    318       get () {
    319         return this.shadowsLocal[this.shadowSelected]
    320       },
    321       set (v) {
    322         set(this.shadowsLocal, this.shadowSelected, v)
    323       }
    324     },
    325     themeValid () {
    326       return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid
    327     },
    328     exportedTheme () {
    329       const saveEverything = (
    330         !this.keepFonts &&
    331         !this.keepShadows &&
    332         !this.keepOpacity &&
    333         !this.keepRoundness &&
    334         !this.keepColor
    335       )
    336 
    337       const source = {
    338         themeEngineVersion: CURRENT_VERSION
    339       }
    340 
    341       if (this.keepFonts || saveEverything) {
    342         source.fonts = this.fontsLocal
    343       }
    344       if (this.keepShadows || saveEverything) {
    345         source.shadows = this.shadowsLocal
    346       }
    347       if (this.keepOpacity || saveEverything) {
    348         source.opacity = this.currentOpacity
    349       }
    350       if (this.keepColor || saveEverything) {
    351         source.colors = this.currentColors
    352       }
    353       if (this.keepRoundness || saveEverything) {
    354         source.radii = this.currentRadii
    355       }
    356 
    357       const theme = {
    358         themeEngineVersion: CURRENT_VERSION,
    359         ...this.previewTheme
    360       }
    361 
    362       return {
    363         // To separate from other random JSON files and possible future source formats
    364         _pleroma_theme_version: 2, theme, source
    365       }
    366     }
    367   },
    368   components: {
    369     ColorInput,
    370     OpacityInput,
    371     RangeInput,
    372     ContrastRatio,
    373     ShadowControl,
    374     FontControl,
    375     TabSwitcher,
    376     Preview,
    377     ExportImport,
    378     Checkbox
    379   },
    380   methods: {
    381     loadTheme (
    382       {
    383         theme,
    384         source,
    385         _pleroma_theme_version: fileVersion
    386       },
    387       origin,
    388       forceUseSource = false
    389     ) {
    390       this.dismissWarning()
    391       if (!source && !theme) {
    392         throw new Error('Can\'t load theme: empty')
    393       }
    394       const version = (origin === 'localStorage' && !theme.colors)
    395         ? 'l1'
    396         : fileVersion
    397       const snapshotEngineVersion = (theme || {}).themeEngineVersion
    398       const themeEngineVersion = (source || {}).themeEngineVersion || 2
    399       const versionsMatch = themeEngineVersion === CURRENT_VERSION
    400       const sourceSnapshotMismatch = (
    401         theme !== undefined &&
    402           source !== undefined &&
    403           themeEngineVersion !== snapshotEngineVersion
    404       )
    405       // Force loading of source if user requested it or if snapshot
    406       // is unavailable
    407       const forcedSourceLoad = (source && forceUseSource) || !theme
    408       if (!(versionsMatch && !sourceSnapshotMismatch) &&
    409           !forcedSourceLoad &&
    410           version !== 'l1' &&
    411           origin !== 'defaults'
    412       ) {
    413         if (sourceSnapshotMismatch && origin === 'localStorage') {
    414           this.themeWarning = {
    415             origin,
    416             themeEngineVersion,
    417             type: 'snapshot_source_mismatch'
    418           }
    419         } else if (!theme) {
    420           this.themeWarning = {
    421             origin,
    422             noActionsPossible: true,
    423             themeEngineVersion,
    424             type: 'no_snapshot_old_version'
    425           }
    426         } else if (!versionsMatch) {
    427           this.themeWarning = {
    428             origin,
    429             noActionsPossible: !source,
    430             themeEngineVersion,
    431             type: 'wrong_version'
    432           }
    433         }
    434       }
    435       this.normalizeLocalState(theme, version, source, forcedSourceLoad)
    436     },
    437     forceLoadLocalStorage () {
    438       this.loadThemeFromLocalStorage(true)
    439     },
    440     dismissWarning () {
    441       this.themeWarning = undefined
    442       this.tempImportFile = undefined
    443     },
    444     forceLoad () {
    445       const { origin } = this.themeWarning
    446       switch (origin) {
    447         case 'localStorage':
    448           this.loadThemeFromLocalStorage(true)
    449           break
    450         case 'file':
    451           this.onImport(this.tempImportFile, true)
    452           break
    453       }
    454       this.dismissWarning()
    455     },
    456     forceSnapshot () {
    457       const { origin } = this.themeWarning
    458       switch (origin) {
    459         case 'localStorage':
    460           this.loadThemeFromLocalStorage(false, true)
    461           break
    462         case 'file':
    463           console.err('Forcing snapshout from file is not supported yet')
    464           break
    465       }
    466       this.dismissWarning()
    467     },
    468     loadThemeFromLocalStorage (confirmLoadSource = false, forceSnapshot = false) {
    469       const {
    470         customTheme: theme,
    471         customThemeSource: source
    472       } = this.$store.getters.mergedConfig
    473       if (!theme && !source) {
    474         // Anon user or never touched themes
    475         this.loadTheme(
    476           this.$store.state.instance.themeData,
    477           'defaults',
    478           confirmLoadSource
    479         )
    480       } else {
    481         this.loadTheme(
    482           {
    483             theme,
    484             source: forceSnapshot ? theme : source
    485           },
    486           'localStorage',
    487           confirmLoadSource
    488         )
    489       }
    490     },
    491     setCustomTheme () {
    492       this.$store.dispatch('setOption', {
    493         name: 'customTheme',
    494         value: {
    495           themeEngineVersion: CURRENT_VERSION,
    496           ...this.previewTheme
    497         }
    498       })
    499       this.$store.dispatch('setOption', {
    500         name: 'customThemeSource',
    501         value: {
    502           themeEngineVersion: CURRENT_VERSION,
    503           shadows: this.shadowsLocal,
    504           fonts: this.fontsLocal,
    505           opacity: this.currentOpacity,
    506           colors: this.currentColors,
    507           radii: this.currentRadii
    508         }
    509       })
    510     },
    511     updatePreviewColorsAndShadows () {
    512       this.previewColors = generateColors({
    513         opacity: this.currentOpacity,
    514         colors: this.currentColors
    515       })
    516       this.previewShadows = generateShadows(
    517         { shadows: this.shadowsLocal, opacity: this.previewTheme.opacity, themeEngineVersion: this.engineVersion },
    518         this.previewColors.theme.colors,
    519         this.previewColors.mod
    520       )
    521     },
    522     onImport (parsed, forceSource = false) {
    523       this.tempImportFile = parsed
    524       this.loadTheme(parsed, 'file', forceSource)
    525     },
    526     importValidator (parsed) {
    527       const version = parsed._pleroma_theme_version
    528       return version >= 1 || version <= 2
    529     },
    530     clearAll () {
    531       this.loadThemeFromLocalStorage()
    532     },
    533 
    534     // Clears all the extra stuff when loading V1 theme
    535     clearV1 () {
    536       Object.keys(this.$data)
    537         .filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
    538         .filter(_ => !v1OnlyNames.includes(_))
    539         .forEach(key => {
    540           set(this.$data, key, undefined)
    541         })
    542     },
    543 
    544     clearRoundness () {
    545       Object.keys(this.$data)
    546         .filter(_ => _.endsWith('RadiusLocal'))
    547         .forEach(key => {
    548           set(this.$data, key, undefined)
    549         })
    550     },
    551 
    552     clearOpacity () {
    553       Object.keys(this.$data)
    554         .filter(_ => _.endsWith('OpacityLocal'))
    555         .forEach(key => {
    556           set(this.$data, key, undefined)
    557         })
    558     },
    559 
    560     clearShadows () {
    561       this.shadowsLocal = {}
    562     },
    563 
    564     clearFonts () {
    565       this.fontsLocal = {}
    566     },
    567 
    568     /**
    569      * This applies stored theme data onto form. Supports three versions of data:
    570      * v3 (version >= 3) - newest version of themes which supports snapshots for better compatiblity
    571      * v2 (version = 2) - newer version of themes.
    572      * v1 (version = 1) - older version of themes (import from file)
    573      * v1l (version = l1) - older version of theme (load from local storage)
    574      * v1 and v1l differ because of way themes were stored/exported.
    575      * @param {Object} theme - theme data (snapshot)
    576      * @param {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type
    577      * @param {Object} source - theme source - this will be used if compatible
    578      * @param {Boolean} source - by default source won't be used if version doesn't match since it might render differently
    579      *                           this allows importing source anyway
    580      */
    581     normalizeLocalState (theme, version = 0, source, forceSource = false) {
    582       let input
    583       if (typeof source !== 'undefined') {
    584         if (forceSource || source.themeEngineVersion === CURRENT_VERSION) {
    585           input = source
    586           version = source.themeEngineVersion
    587         } else {
    588           input = theme
    589         }
    590       } else {
    591         input = theme
    592       }
    593 
    594       const radii = input.radii || input
    595       const opacity = input.opacity
    596       const shadows = input.shadows || {}
    597       const fonts = input.fonts || {}
    598       const colors = !input.themeEngineVersion
    599         ? colors2to3(input.colors || input)
    600         : input.colors || input
    601 
    602       if (version === 0) {
    603         if (input.version) version = input.version
    604         // Old v1 naming: fg is text, btn is foreground
    605         if (typeof colors.text === 'undefined' && typeof colors.fg !== 'undefined') {
    606           version = 1
    607         }
    608         // New v2 naming: text is text, fg is foreground
    609         if (typeof colors.text !== 'undefined' && typeof colors.fg !== 'undefined') {
    610           version = 2
    611         }
    612       }
    613 
    614       this.engineVersion = version
    615 
    616       // Stuff that differs between V1 and V2
    617       if (version === 1) {
    618         this.fgColorLocal = rgb2hex(colors.btn)
    619         this.textColorLocal = rgb2hex(colors.fg)
    620       }
    621 
    622       if (!this.keepColor) {
    623         this.clearV1()
    624         const keys = new Set(version !== 1 ? Object.keys(SLOT_INHERITANCE) : [])
    625         if (version === 1 || version === 'l1') {
    626           keys
    627             .add('bg')
    628             .add('link')
    629             .add('cRed')
    630             .add('cBlue')
    631             .add('cGreen')
    632             .add('cOrange')
    633         }
    634 
    635         keys.forEach(key => {
    636           const color = colors[key]
    637           const hex = rgb2hex(colors[key])
    638           this[key + 'ColorLocal'] = hex === '#aN' ? color : hex
    639         })
    640       }
    641 
    642       if (opacity && !this.keepOpacity) {
    643         this.clearOpacity()
    644         Object.entries(opacity).forEach(([k, v]) => {
    645           if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return
    646           this[k + 'OpacityLocal'] = v
    647         })
    648       }
    649 
    650       if (!this.keepRoundness) {
    651         this.clearRoundness()
    652         Object.entries(radii).forEach(([k, v]) => {
    653           // 'Radius' is kept mostly for v1->v2 localstorage transition
    654           const key = k.endsWith('Radius') ? k.split('Radius')[0] : k
    655           this[key + 'RadiusLocal'] = v
    656         })
    657       }
    658 
    659       if (!this.keepShadows) {
    660         this.clearShadows()
    661         if (version === 2) {
    662           this.shadowsLocal = shadows2to3(shadows, this.previewTheme.opacity)
    663         } else {
    664           this.shadowsLocal = shadows
    665         }
    666         this.shadowSelected = this.shadowsAvailable[0]
    667       }
    668 
    669       if (!this.keepFonts) {
    670         this.clearFonts()
    671         this.fontsLocal = fonts
    672       }
    673     }
    674   },
    675   watch: {
    676     currentRadii () {
    677       try {
    678         this.previewRadii = generateRadii({ radii: this.currentRadii })
    679         this.radiiInvalid = false
    680       } catch (e) {
    681         this.radiiInvalid = true
    682         console.warn(e)
    683       }
    684     },
    685     shadowsLocal: {
    686       handler () {
    687         if (Object.getOwnPropertyNames(this.previewColors).length === 1) return
    688         try {
    689           this.updatePreviewColorsAndShadows()
    690           this.shadowsInvalid = false
    691         } catch (e) {
    692           this.shadowsInvalid = true
    693           console.warn(e)
    694         }
    695       },
    696       deep: true
    697     },
    698     fontsLocal: {
    699       handler () {
    700         try {
    701           this.previewFonts = generateFonts({ fonts: this.fontsLocal })
    702           this.fontsInvalid = false
    703         } catch (e) {
    704           this.fontsInvalid = true
    705           console.warn(e)
    706         }
    707       },
    708       deep: true
    709     },
    710     currentColors () {
    711       try {
    712         this.updatePreviewColorsAndShadows()
    713         this.colorsInvalid = false
    714         this.shadowsInvalid = false
    715       } catch (e) {
    716         this.colorsInvalid = true
    717         this.shadowsInvalid = true
    718         console.warn(e)
    719       }
    720     },
    721     currentOpacity () {
    722       try {
    723         this.updatePreviewColorsAndShadows()
    724       } catch (e) {
    725         console.warn(e)
    726       }
    727     },
    728     selected () {
    729       this.dismissWarning()
    730       if (this.selectedVersion === 1) {
    731         if (!this.keepRoundness) {
    732           this.clearRoundness()
    733         }
    734 
    735         if (!this.keepShadows) {
    736           this.clearShadows()
    737         }
    738 
    739         if (!this.keepOpacity) {
    740           this.clearOpacity()
    741         }
    742 
    743         if (!this.keepColor) {
    744           this.clearV1()
    745 
    746           this.bgColorLocal = this.selected[1]
    747           this.fgColorLocal = this.selected[2]
    748           this.textColorLocal = this.selected[3]
    749           this.linkColorLocal = this.selected[4]
    750           this.cRedColorLocal = this.selected[5]
    751           this.cGreenColorLocal = this.selected[6]
    752           this.cBlueColorLocal = this.selected[7]
    753           this.cOrangeColorLocal = this.selected[8]
    754         }
    755       } else if (this.selectedVersion >= 2) {
    756         this.normalizeLocalState(this.selected.theme, 2, this.selected.source)
    757       }
    758     }
    759   }
    760 }