forked from daren.hsu/line_push
258 lines
6.2 KiB
JavaScript
258 lines
6.2 KiB
JavaScript
/* eslint-disable no-multi-spaces */
|
|
// Extensions
|
|
import { Service } from '../service'; // Utilities
|
|
|
|
import * as ThemeUtils from './utils';
|
|
import { getNestedValue } from '../../util/helpers'; // Types
|
|
|
|
import Vue from 'vue';
|
|
export class Theme extends Service {
|
|
constructor(preset) {
|
|
super();
|
|
this.disabled = false;
|
|
this.isDark = null;
|
|
this.vueInstance = null;
|
|
this.vueMeta = null;
|
|
const {
|
|
dark,
|
|
disable,
|
|
options,
|
|
themes
|
|
} = preset[Theme.property];
|
|
this.dark = Boolean(dark);
|
|
this.defaults = this.themes = themes;
|
|
this.options = options;
|
|
|
|
if (disable) {
|
|
this.disabled = true;
|
|
return;
|
|
}
|
|
|
|
this.themes = {
|
|
dark: this.fillVariant(themes.dark, true),
|
|
light: this.fillVariant(themes.light, false)
|
|
};
|
|
} // When setting css, check for element
|
|
// and apply new values
|
|
|
|
|
|
set css(val) {
|
|
if (this.vueMeta) {
|
|
if (this.isVueMeta23) {
|
|
this.applyVueMeta23();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
this.checkOrCreateStyleElement() && (this.styleEl.innerHTML = val);
|
|
}
|
|
|
|
set dark(val) {
|
|
const oldDark = this.isDark;
|
|
this.isDark = val; // Only apply theme after dark
|
|
// has already been set before
|
|
|
|
oldDark != null && this.applyTheme();
|
|
}
|
|
|
|
get dark() {
|
|
return Boolean(this.isDark);
|
|
} // Apply current theme default
|
|
// only called on client side
|
|
|
|
|
|
applyTheme() {
|
|
if (this.disabled) return this.clearCss();
|
|
this.css = this.generatedStyles;
|
|
}
|
|
|
|
clearCss() {
|
|
this.css = '';
|
|
} // Initialize theme for SSR and SPA
|
|
// Attach to ssrContext head or
|
|
// apply new theme to document
|
|
|
|
|
|
init(root, ssrContext) {
|
|
if (this.disabled) return;
|
|
/* istanbul ignore else */
|
|
|
|
if (root.$meta) {
|
|
this.initVueMeta(root);
|
|
} else if (ssrContext) {
|
|
this.initSSR(ssrContext);
|
|
}
|
|
|
|
this.initTheme();
|
|
} // Allows for you to set target theme
|
|
|
|
|
|
setTheme(theme, value) {
|
|
this.themes[theme] = Object.assign(this.themes[theme], value);
|
|
this.applyTheme();
|
|
} // Reset theme defaults
|
|
|
|
|
|
resetThemes() {
|
|
this.themes.light = Object.assign({}, this.defaults.light);
|
|
this.themes.dark = Object.assign({}, this.defaults.dark);
|
|
this.applyTheme();
|
|
} // Check for existence of style element
|
|
|
|
|
|
checkOrCreateStyleElement() {
|
|
this.styleEl = document.getElementById('vuetify-theme-stylesheet');
|
|
/* istanbul ignore next */
|
|
|
|
if (this.styleEl) return true;
|
|
this.genStyleElement(); // If doesn't have it, create it
|
|
|
|
return Boolean(this.styleEl);
|
|
}
|
|
|
|
fillVariant(theme = {}, dark) {
|
|
const defaultTheme = this.themes[dark ? 'dark' : 'light'];
|
|
return Object.assign({}, defaultTheme, theme);
|
|
} // Generate the style element
|
|
// if applicable
|
|
|
|
|
|
genStyleElement() {
|
|
/* istanbul ignore if */
|
|
if (typeof document === 'undefined') return;
|
|
/* istanbul ignore next */
|
|
|
|
this.styleEl = document.createElement('style');
|
|
this.styleEl.type = 'text/css';
|
|
this.styleEl.id = 'vuetify-theme-stylesheet';
|
|
|
|
if (this.options.cspNonce) {
|
|
this.styleEl.setAttribute('nonce', this.options.cspNonce);
|
|
}
|
|
|
|
document.head.appendChild(this.styleEl);
|
|
}
|
|
|
|
initVueMeta(root) {
|
|
this.vueMeta = root.$meta();
|
|
|
|
if (this.isVueMeta23) {
|
|
// vue-meta needs to apply after mounted()
|
|
root.$nextTick(() => {
|
|
this.applyVueMeta23();
|
|
});
|
|
return;
|
|
}
|
|
|
|
const metaKeyName = typeof this.vueMeta.getOptions === 'function' ? this.vueMeta.getOptions().keyName : 'metaInfo';
|
|
const metaInfo = root.$options[metaKeyName] || {};
|
|
|
|
root.$options[metaKeyName] = () => {
|
|
metaInfo.style = metaInfo.style || [];
|
|
const vuetifyStylesheet = metaInfo.style.find(s => s.id === 'vuetify-theme-stylesheet');
|
|
|
|
if (!vuetifyStylesheet) {
|
|
metaInfo.style.push({
|
|
cssText: this.generatedStyles,
|
|
type: 'text/css',
|
|
id: 'vuetify-theme-stylesheet',
|
|
nonce: (this.options || {}).cspNonce
|
|
});
|
|
} else {
|
|
vuetifyStylesheet.cssText = this.generatedStyles;
|
|
}
|
|
|
|
return metaInfo;
|
|
};
|
|
}
|
|
|
|
applyVueMeta23() {
|
|
const {
|
|
set
|
|
} = this.vueMeta.addApp('vuetify');
|
|
set({
|
|
style: [{
|
|
cssText: this.generatedStyles,
|
|
type: 'text/css',
|
|
id: 'vuetify-theme-stylesheet',
|
|
nonce: this.options.cspNonce
|
|
}]
|
|
});
|
|
}
|
|
|
|
initSSR(ssrContext) {
|
|
// SSR
|
|
const nonce = this.options.cspNonce ? ` nonce="${this.options.cspNonce}"` : '';
|
|
ssrContext.head = ssrContext.head || '';
|
|
ssrContext.head += `<style type="text/css" id="vuetify-theme-stylesheet"${nonce}>${this.generatedStyles}</style>`;
|
|
}
|
|
|
|
initTheme() {
|
|
// Only watch for reactivity on client side
|
|
if (typeof document === 'undefined') return; // If we get here somehow, ensure
|
|
// existing instance is removed
|
|
|
|
if (this.vueInstance) this.vueInstance.$destroy(); // Use Vue instance to track reactivity
|
|
// TODO: Update to use RFC if merged
|
|
// https://github.com/vuejs/rfcs/blob/advanced-reactivity-api/active-rfcs/0000-advanced-reactivity-api.md
|
|
|
|
this.vueInstance = new Vue({
|
|
data: {
|
|
themes: this.themes
|
|
},
|
|
watch: {
|
|
themes: {
|
|
immediate: true,
|
|
deep: true,
|
|
handler: () => this.applyTheme()
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
get currentTheme() {
|
|
const target = this.dark ? 'dark' : 'light';
|
|
return this.themes[target];
|
|
}
|
|
|
|
get generatedStyles() {
|
|
const theme = this.parsedTheme;
|
|
/* istanbul ignore next */
|
|
|
|
const options = this.options || {};
|
|
let css;
|
|
|
|
if (options.themeCache != null) {
|
|
css = options.themeCache.get(theme);
|
|
/* istanbul ignore if */
|
|
|
|
if (css != null) return css;
|
|
}
|
|
|
|
css = ThemeUtils.genStyles(theme, options.customProperties);
|
|
|
|
if (options.minifyTheme != null) {
|
|
css = options.minifyTheme(css);
|
|
}
|
|
|
|
if (options.themeCache != null) {
|
|
options.themeCache.set(theme, css);
|
|
}
|
|
|
|
return css;
|
|
}
|
|
|
|
get parsedTheme() {
|
|
return ThemeUtils.parse(this.currentTheme || {}, undefined, getNestedValue(this.options, ['variations'], true));
|
|
} // Is using v2.3 of vue-meta
|
|
// https://github.com/nuxt/vue-meta/releases/tag/v2.3.0
|
|
|
|
|
|
get isVueMeta23() {
|
|
return typeof this.vueMeta.addApp === 'function';
|
|
}
|
|
|
|
}
|
|
Theme.property = 'theme';
|
|
//# sourceMappingURL=index.js.map
|