jupyter-vuetify
Version:
Jupyter widgets based on vuetify UI components
199 lines (179 loc) • 5.61 kB
JavaScript
import { VueView, createViewContext, vueRender } from "jupyter-vue";
import "vuetify/styles";
import colors from "vuetify/lib/util/colors.mjs";
import { createVuetify, useTheme } from "vuetify";
import * as components from "vuetify/components";
import * as labComponents from "vuetify/labs/components";
import * as directives from "vuetify/directives";
import { ThemeColorsModel, ThemeModel } from "./Themes";
import { addApp } from "./VuetifyApp";
// Every widget view gets its own Vue app, but all views for one widget manager
// should share a single Vuetify plugin and theme state.
const managerStateByWidgetManager = new WeakMap();
function getManagerState(widgetManager) {
let managerState = managerStateByWidgetManager.get(widgetManager);
if (!managerState) {
managerState = {
initializedThemes: new WeakSet(),
vuetify: createVuetify({
components: {
...components,
...labComponents,
},
directives,
}),
};
managerStateByWidgetManager.set(widgetManager, managerState);
}
return managerState;
}
const observer = new MutationObserver((mutationList) => {
for (const mutation of mutationList) {
if (mutation.type === "childList") {
const overlay = document.querySelector(".v-overlay-container");
if (overlay) {
overlay.classList.add("vuetify-styles");
overlay.addEventListener("keydown", (event) => {
event.stopPropagation();
});
}
}
}
});
observer.observe(document.body, { childList: true });
export class VuetifyView extends VueView {
addPlugins(vueApp) {
super.addPlugins(vueApp);
const { vuetify } = getManagerState(this.model.widget_manager);
this.el.classList.add("vuetify-styles");
document.querySelector("html").style.fontSize = "16px";
vueApp.use(vuetify);
addApp(vueApp);
}
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(useTheme());
}
/** @override */
vueComponent() {
const view = this;
return {
provide: {
viewCtx: createViewContext(view),
},
setup: () => {
view.onSetup();
return () => vueRender(view.model, view);
},
};
}
setupTheme(theme) {
if (!this.themeModel) {
return;
}
const managerState = getManagerState(this.model.widget_manager);
if (!managerState.initializedThemes.has(theme)) {
initializeTheme(
theme,
this.themeModel,
this.themeLightModel,
this.themeDarkModel
);
managerState.initializedThemes.add(theme);
}
}
}
function initializeTheme(theme, themeModel, themeLightModel, themeDarkModel) {
if (ThemeModel.themeManager) {
const setAutoTheme = () => {
if (themeModel.get("dark") === null) {
const isDark = document.body.dataset.jpThemeLight === "false";
theme.global.name.value = isDark ? "dark" : "light";
themeModel.set("dark_jlab", isDark);
themeModel.save_changes();
}
};
ThemeModel.themeManager.themeChanged.connect(() => {
setAutoTheme();
});
setAutoTheme();
}
const onDark = () => {
theme.global.name.value = themeModel.get("dark") ? "dark" : "light";
};
const onColorsLight = () => {
mergeThemeColors(theme, "light", getColors(themeLightModel));
};
onColorsLight();
const onColorsDark = () => {
mergeThemeColors(theme, "dark", getColors(themeDarkModel));
};
onColorsDark();
if (themeModel.get("dark") !== null) {
onDark();
} else if (document.body.dataset.jpThemeLight) {
const isDark = document.body.dataset.jpThemeLight === "false";
theme.global.name.value = isDark ? "dark" : "light";
themeModel.set("dark_jlab", isDark);
themeModel.save_changes();
} else if (document.body.classList.contains("theme-dark")) {
theme.global.name.value = "dark";
themeModel.set("dark", true);
themeModel.save_changes();
} else if (document.body.classList.contains("theme-light")) {
themeModel.set("dark", false);
themeModel.save_changes();
}
themeModel.on("change:dark", onDark);
themeLightModel.on("change", onColorsLight);
themeDarkModel.on("change", onColorsDark);
}
function mergeThemeColors(theme, themeName, colors) {
const themeConfig = theme.themes.value[themeName];
themeConfig.colors = {
...(themeConfig.colors || {}),
...colors,
};
}
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) {
const colors = Object.fromEntries(
Object.entries(colorModel.attributes)
.filter(
([key]) =>
!key.startsWith("_") &&
!["accent", "anchor", "custom_theme_colors"].includes(key)
)
.map(([key, value]) => [
key.replace(/_/g, "-"),
value.startsWith("colors.") ? parseColor(value) : value,
])
);
return {
...colors,
...(colorModel.get("custom_theme_colors") || {}),
};
}