logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git

theme_tab.js (23689B)


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