logo

pleroma-fe

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

theme_tab.js (23605B)


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