Skip to content

Commit

Permalink
feat: add theme support
Browse files Browse the repository at this point in the history
  • Loading branch information
mariobuikhuizen committed Nov 15, 2023
1 parent 294c9b1 commit fdb1371
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 92 deletions.
47 changes: 37 additions & 10 deletions ipyvuetify/Themes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,33 @@ class Themes:
def __init__(self):
self.light = ThemeColors(
_theme_name="light",
primary="#1976D2",
secondary="#424242",
accent="#82B1FF",
error="#FF5252",
background="#FFFFFF",
surface="#FFFFFF",
surface_bright="#FFFFFF",
surface_variant="#424242",
on_surface_variant="#EEEEEE",
primary="#6200EE",
primary_darken_1="#3700B3",
secondary="#03DAC6",
secondary_darken_1="#018786",
error="#B00020",
info="#2196F3",
success="#4CAF50",
warning="#FB8C00",
)

self.dark = ThemeColors(
_theme_name="dark",
primary="#2196F3",
secondary="#424242",
accent="#FF4081",
error="#FF5252",
background="#121212",
surface="#212121",
surface_bright="#ccbfd6",
surface_variant="#a3a3a3",
on_surface_variant="#424242",
primary="#BB86FC",
primary_darken_1="#3700B3",
secondary="#03DAC5",
secondary_darken_1="#03DAC5",
error="#CF6679",
info="#2196F3",
success="#4CAF50",
warning="#FB8C00",
Expand All @@ -48,6 +60,14 @@ def __init__(self):
self.themes = Themes()


class ColorNotAvailable(Unicode):
def get(self, *ignored):
raise AttributeError(f"The theme color '{self.name}' is no longer available in Vuetify 3")

def set(self, *ignored):
raise AttributeError(f"The theme color '{self.name}' is no longer available in Vuetify 3")


class ThemeColors(Widget):

_model_name = Unicode("ThemeColorsModel").tag(sync=True)
Expand All @@ -60,14 +80,21 @@ class ThemeColors(Widget):

_theme_name = Unicode().tag(sync=True)

accent = ColorNotAvailable()
anchor = ColorNotAvailable()
background = Unicode().tag(sync=True)
surface = Unicode().tag(sync=True)
surface_bright = Unicode().tag(sync=True)
surface_variant = Unicode().tag(sync=True)
on_surface_variant = Unicode().tag(sync=True)
primary = Unicode().tag(sync=True)
primary_darken_1 = Unicode().tag(sync=True)
secondary = Unicode().tag(sync=True)
accent = Unicode().tag(sync=True)
secondary_darken_1 = Unicode().tag(sync=True)
error = Unicode().tag(sync=True)
info = Unicode().tag(sync=True)
success = Unicode().tag(sync=True)
warning = Unicode().tag(sync=True)
anchor = Unicode(None, allow_none=True).tag(sync=True)


theme = Theme()
Expand Down
83 changes: 7 additions & 76 deletions js/src/Themes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/* eslint camelcase: off */
import { WidgetModel } from "@jupyter-widgets/base";
// import colors from "@mariobuikhuizen/vuetify/lib/util/colors";
// import vuetify from "./plugins/vuetify";

export class ThemeModel extends WidgetModel {
defaults() {
Expand All @@ -20,40 +18,6 @@ export class ThemeModel extends WidgetModel {

constructor(...args) {
super(...args);

// if (!vuetify) {
// return;
// }

// if (ThemeModel.themeManager) {
// ThemeModel.themeManager.themeChanged.connect(() => {
// if (this.get("dark") === null) {
// vuetify.framework.theme.dark =
// document.body.dataset.jpThemeLight === "false";
// this.set("dark_jlab", vuetify.framework.theme.dark);
// this.save_changes();
// }
// }, this);
// }
//
// if (this.get("dark") !== null) {
// vuetify.framework.theme.dark = this.get("dark");
// } else if (document.body.dataset.jpThemeLight) {
// vuetify.framework.theme.dark =
// document.body.dataset.jpThemeLight === "false";
// this.set("dark_jlab", vuetify.framework.theme.dark);
// this.save_changes();
// } else if (document.body.classList.contains("theme-dark")) {
// vuetify.framework.theme.dark = true;
// this.set("dark", true);
// this.save_changes();
// } else if (document.body.classList.contains("theme-light")) {
// this.set("dark", false);
// this.save_changes();
// }
// this.on("change:dark", () => {
// vuetify.framework.theme.dark = this.get("dark");
// });
}
}

Expand All @@ -71,61 +35,28 @@ export class ThemeColorsModel extends WidgetModel {
_view_module_version: "0.1.11",
_model_module_version: "0.1.11",
_theme_name: null,
background: null,
surface: null,
surface_bright: null,
surface_variant: null,
on_surface_variant: null,
primary: null,
primary_darken_1: null,
secondary: null,
accent: null,
secondary_darken_1: null,
error: null,
info: null,
success: null,
warning: null,
anchor: null,
},
};
}

constructor(...args) {
super(...args);

// if (!vuetify) {
// return;
// }

const themeName = this.get("_theme_name");

// this.keys()
// .filter((prop) => !prop.startsWith("_"))
// .forEach((prop) => {
// vuetify.framework.theme.themes[themeName][prop] = convertColor(
// this.get(prop)
// );
// this.on(`change:${prop}`, () => {
// vuetify.framework.theme.themes[themeName][prop] = convertColor(
// this.get(prop)
// );
// });
// });
}
}

ThemeColorsModel.serializers = {
...WidgetModel.serializers,
};

function convertColor(colorStr) {
if (colorStr == null) {
return null;
}

if (colorStr.startsWith("colors")) {
const parts = colorStr.split(".").slice(1);
let result = colors;

parts.forEach((part) => {
result = result[part];
});

return result;
}

return colorStr;
}
118 changes: 112 additions & 6 deletions js/src/VuetifyView.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as Vue from "vue"; // eslint-disable-line import/no-extraneous-dependencies
import { VueView, createViewContext, vueRender } from "jupyter-vue";
import "vuetify/styles";
import { createVuetify } from "vuetify";
import colors from "vuetify/lib/util/colors.mjs";
import { createVuetify, useTheme } from "vuetify";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";
import { VDataTable } from "vuetify/labs/VDataTable";
import { ThemeColorsModel, ThemeModel } from "./Themes";

const observer = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
Expand All @@ -24,11 +26,6 @@ observer.observe(document.body, { childList: true });

export class VuetifyView extends VueView {
addPlugins(vueApp) {
console.log(
Vue.compile(
`<v-btn v-model="xx" :color="'primary'" :key="cc">test</v-btn>`
)
);
super.addPlugins(vueApp);
const vuetify = createVuetify({
components: { ...components, VDataTable },
Expand All @@ -39,6 +36,28 @@ export class VuetifyView extends VueView {
vueApp.use(vuetify);
}

async beforeViewRender() {
await super.beforeViewRender();
const models = await Promise.all(
Object.values(this.model.widget_manager._models)
);
this.themeModel = models.find((m) => m instanceof ThemeModel);
models
.filter((m) => m instanceof ThemeColorsModel)
.forEach((m) => {
if (m.get("_theme_name") === "light") {
this.themeLightModel = m;
} else if (m.get("_theme_name") === "dark") {
this.themeDarkModel = m;
}
});
}

onSetup() {
super.onSetup();
this.setupTheme();
}

/* used in pages using nodeps */
vueRender() {
return Vue.h({
Expand All @@ -50,4 +69,91 @@ export class VuetifyView extends VueView {
},
});
}

setupTheme() {
this.theme = useTheme();

if (ThemeModel.themeManager) {
const setAutoTheme = () => {
if (this.themeModel.get("dark") === null) {
const isDark = document.body.dataset.jpThemeLight === "false";
this.theme.global.name.value = isDark ? "dark" : "light";
this.themeModel.set("dark_jlab", isDark);
this.themeModel.save_changes();
}
};
ThemeModel.themeManager.themeChanged.connect(() => {
setAutoTheme();
}, this);
setAutoTheme();
}

const onDark = () => {
this.theme.global.name.value = this.themeModel.get("dark")
? "dark"
: "light";
};

const onColorsLight = () => {
this.theme.themes.value.light.colors = getColors(this.themeLightModel);
};
onColorsLight();

const onColorsDark = () => {
this.theme.themes.value.dark.colors = getColors(this.themeDarkModel);
};
onColorsDark();

if (this.themeModel.get("dark") !== null) {
onDark();
} else if (document.body.dataset.jpThemeLight) {
const isDark = document.body.dataset.jpThemeLight === "false";
this.theme.global.name.value = isDark ? "dark" : "light";
this.themeModel.set("dark_jlab", isDark);
this.themeModel.save_changes();
} else if (document.body.classList.contains("theme-dark")) {
this.theme.global.name.value = "dark";
this.themeModel.set("dark", true);
this.themeModel.save_changes();
} else if (document.body.classList.contains("theme-light")) {
this.themeModel.set("dark", false);
this.themeModel.save_changes();
}

Vue.onMounted(() => {
this.themeModel.on("change:dark", onDark);
this.themeLightModel.on("change", onColorsLight);
this.themeDarkModel.on("change", onColorsDark);
});

Vue.onUnmounted(() => {
this.themeModel.off("change:dark", onDark);
this.themeLightModel.off("change", onColorsLight);
this.themeDarkModel.off("change", onColorsDark);
});
}
}

function parseColor(colorStr) {
const parts = colorStr.split(".").slice(1);
let result = colors;

parts.forEach((part) => {
result = result[part];
});

return typeof result === "string" ? result : result.base;
}

function getColors(colorModel) {
return _.mapKeys(
_.mapValues(
_.pickBy(
{ ...colorModel.attributes },
(v, k) => !k.startsWith("_") && k !== "accent" && k !== "anchor"
),
(v) => (v.startsWith("colors.") ? parseColor(v) : v)
),
(v, k) => k.replace(/_/g, "-")
);
}

0 comments on commit fdb1371

Please sign in to comment.