Skip to content

Commit

Permalink
SAK-49621 Fix dark mode issues in Gateway iframes, input forms, and i…
Browse files Browse the repository at this point in the history
…mprove theme scripts (sakaiproject#12428)
  • Loading branch information
kunaljaykam authored Mar 13, 2024
1 parent 4c091d9 commit 996a2f9
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,6 @@
}
}

.form-floating > label {
color: var(--sakai-color-gray--darker-6);
}

#sakai-system-indicators {

button {
Expand Down
22 changes: 22 additions & 0 deletions library/src/skins/default/src/sass/themes/_dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,28 @@
--bs-btn-color: white;
}

input.form-control {
background-color: var(--sakai-background-color-2);
color: var(--sakai-text-color-1);

&:focus,
&:active,
&:-webkit-autofill {
background-color: var(--sakai-background-color-3);
color: var(--sakai-text-color-2);
}
}

input.form-control:-webkit-autofill,
input.form-control:-webkit-autofill:hover,
input.form-control:-webkit-autofill:focus,
input.form-control:-webkit-autofill:active {
transition: background-color 5000s ease-in-out 0s, color 5000s ease-in-out 0s;
--webkit-text-fill-color: var(--sakai-text-color-1) !important;
background-color: var(--sakai-background-color-2) !important;
}


.dropdown-menu {
--bs-dropdown-bg: var(--sakai-background-color-3);
--bs-dropdown-color: var(--sakai-text-color-1);
Expand Down
1 change: 1 addition & 0 deletions library/src/webapp/bundled-js/sakai.morpheus.i18n.utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(function ($) {

if (!portal.loggedIn) {
localStorage.removeItem("last-sakai-user");
return;
}

Expand Down
48 changes: 21 additions & 27 deletions library/src/webapp/js/headscripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -835,33 +835,27 @@ function includeWebjarLibrary(library, options = {}) {

}

/**
* Applies a theme if unset, serving as a fallback without portal.theme.switcher.js.
*/
function applyThemeIfMissing() {

const sakaiTheme = localStorage.getItem('sakai-theme');
if (sakaiTheme) {
const htmlElement = document.documentElement;
if (!htmlElement.classList.contains(sakaiTheme)) {
htmlElement.classList.forEach(className => {
if (className.startsWith('sakaiUserTheme-')) {
htmlElement.classList.remove(className);
}
});

htmlElement.classList.add(sakaiTheme);
console.debug('Theme added to page:', sakaiTheme);
}
}
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', applyThemeIfMissing);
} else {
applyThemeIfMissing();
}

// Ensures consistent theming across all Sakai pages by dynamically loading a theme
// switcher script, which applies a user or system-preferred theme class to the document
if (!window.themeClassInit) {
window.themeClassInit = true;
document.addEventListener('DOMContentLoaded', () => {
if (window.top === window.self && ![...document.documentElement.classList].some(c => c.startsWith('sakaiUserTheme-'))) {
const script = document.createElement('script');
script.src = '/library/js/portal/portal.theme.switcher.js';
script.onload = async () => {
try {
portal.addCssClassToMarkup(await portal.getCurrentSetTheme());
} catch (error) {
console.error('Theme error:', error);
}
};
script.onerror = () => console.error('Failed to load script');
document.head.appendChild(script);
}
});
}

// Return the breakpoint between small and medium sized displays - for morpheus currently the same
function portalSmallBreakPoint() { return 800; }
function portalMediumBreakPoint() { return 800; }
Expand Down
115 changes: 68 additions & 47 deletions library/src/webapp/js/portal/portal.theme.switcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,34 @@ portal.lightThemeClass = 'sakaiUserTheme-light';
portal.darkThemeClass = 'sakaiUserTheme-dark';

portal.toggleDarkTheme = () => {
// toggle the dark theme switch to the opposite state
portal.darkThemeSwitcher && portal.darkThemeSwitcher.getAttribute("aria-checked") === "false" ? portal.enableDarkTheme() : portal.enableLightTheme();
};

portal.setDarkThemeSwitcherToggle = onOff => {
portal.darkThemeSwitcher?.setAttribute("aria-checked", onOff.toString());
};

portal.addCssClassToMarkup = themeClass => {

document.documentElement.classList.remove(portal.darkThemeClass, portal.lightThemeClass, portal.defaultThemeClass);
document.documentElement.classList.add(themeClass);
};
portal.addCssClassToMarkup = themeClass => document.documentElement.classList.add(themeClass);

portal.removeCssClassFromMarkup = themeClass => document.documentElement.classList.remove(themeClass);

portal.isOsDarkThemeSet = () => window.matchMedia('(prefers-color-scheme: dark)').matches;

portal.setPortalThemeUserPref = theme => {

if (portal?.user?.id) {
const url = `/direct/userPrefs/updateKey/${portal.user.id}/sakai:portal:theme?theme=${theme}`;
fetch(url, { method: "PUT", credentials: "include" })
.then(r => {
if (!r.ok) {
throw new Error(`Network error while updating theme pref at ${url}`);
}
localStorage.setItem('sakai-theme', theme);
})
.catch(error => console.error(error));
}
const url = `/direct/userPrefs/updateKey/${portal.user.id}/sakai:portal:theme?theme=${theme}`;
fetch(url, { method: "PUT", credentials: "include" })
.then(r => {

if (!r.ok) {
throw new Error(`Network error while updating theme pref at ${url}`);
}
})
.catch (error => console.error(error));
};


portal.getCurrentThemeClass = () => {

if (document.documentElement.classList.contains(portal.darkThemeClass)) {
Expand All @@ -49,65 +45,90 @@ portal.getCurrentThemeClass = () => {
return portal.defaultThemeClass;
};

portal.updateIframeTheme = (themeClass) => {
portal.updateIframeTheme = () => {

document.querySelectorAll('iframe').forEach(iframe => {
if (iframe.contentDocument) {
iframe.contentDocument.documentElement.classList.remove(portal.darkThemeClass, portal.lightThemeClass, portal.defaultThemeClass);
iframe.contentDocument.documentElement.classList.add(themeClass);
if (iframe.contentDocument?.documentElement) {
iframe.contentDocument.documentElement.classList.remove(portal.defaultThemeClass, portal.lightThemeClass, portal.darkThemeClass);
iframe.contentDocument.documentElement.classList.add(portal.getCurrentThemeClass());
}
});
};

portal.addIframeLoadListeners = () => {
document.addEventListener('DOMContentLoaded', () => {

document.querySelectorAll('iframe').forEach(iframe => {
iframe.addEventListener('load', () => portal.updateIframeTheme(portal.getCurrentThemeClass()));
iframe.addEventListener('load', portal.updateIframeTheme);
});
};
portal.updateIframeTheme();
});



portal.enableDarkTheme = () => {

portal.setDarkThemeSwitcherToggle(true);
portal.removeCssClassFromMarkup(portal.defaultThemeClass);
portal.removeCssClassFromMarkup(portal.lightThemeClass);
portal.addCssClassToMarkup(portal.darkThemeClass);
portal.setPortalThemeUserPref(portal.darkThemeClass);
portal.updateIframeTheme(portal.darkThemeClass);
portal.updateIframeTheme();
portal.darkThemeSwitcher.title = portal.i18n.theme_switch_to_light;
};

portal.enableLightTheme = () => {

portal.setDarkThemeSwitcherToggle(false);
portal.removeCssClassFromMarkup(portal.defaultThemeClass);
portal.removeCssClassFromMarkup(portal.darkThemeClass);
portal.addCssClassToMarkup(portal.lightThemeClass);
portal.setPortalThemeUserPref(portal.lightThemeClass);
portal.updateIframeTheme(portal.lightThemeClass);
portal.updateIframeTheme();
portal.darkThemeSwitcher.title = portal.i18n.theme_switch_to_dark;
};

document.addEventListener('DOMContentLoaded', () => {

let themeToApply = portal.isOsDarkThemeSet() ? portal.darkThemeClass : portal.lightThemeClass;
if (!portal.darkThemeSwitcher) {
// If the switcher is not available, directly apply the OS theme preference
portal.addCssClassToMarkup(themeToApply);
} else {
// If the user is logged in and theme is not set or if the switcher is available, follow the logic based on user preference or auto-detect
if (portal?.user?.id && portal.userTheme === portal.defaultThemeClass) {
themeToApply = portal.isOsDarkThemeSet() ? portal.darkThemeClass : portal.lightThemeClass;
} else if (portal.userTheme) {
themeToApply = portal.userTheme;
}
portal.addCssClassToMarkup(themeToApply);
portal.updateIframeTheme(themeToApply);
portal.addIframeLoadListeners();
// if the dark theme switch is on the page, attach listener to dark theme toggle switch
portal.darkThemeSwitcher?.addEventListener('click', portal.toggleDarkTheme, false);

if (portal.userThemeAutoDetectDark) {

portal.darkThemeSwitcher.addEventListener('click', portal.toggleDarkTheme, false);
if (portal.user.id) {
// only check for unset theme preference because light and dark themes are already set by Java
if (portal.userTheme === portal.defaultThemeClass) {
// if the user has dark mode set on their OS, enable dark mode
portal.isOsDarkThemeSet() ? portal.enableDarkTheme() : portal.setPortalThemeUserPref(portal.lightThemeClass);
}
} else if (portal.isOsDarkThemeSet()) {
// just add the dark theme to the markup if not logged in and the user has dark mode set on their OS (no prefs to save)
portal.addCssClassToMarkup(portal.darkThemeClass);
}
}

// Ensure the switch toggle state matches the applied theme
if (themeToApply === portal.darkThemeClass) {
portal.setDarkThemeSwitcherToggle(true);
} else {
portal.setDarkThemeSwitcherToggle(false);
if (document.documentElement.classList.contains(portal.darkThemeClass)) {
// the dark theme switch toggle is off by default, so toggle it to on if dark theme is enabled
portal.setDarkThemeSwitcherToggle(true);
}


portal.getCurrentSetTheme = async () => {

const userId = localStorage.getItem('last-sakai-user');
if (!userId) return portal.isOsDarkThemeSet() ? portal.darkThemeClass : portal.lightThemeClass;

const url = `/direct/userPrefs/key/${userId}/sakai:portal:theme.json`;

try {
const response = await fetch(url, {
credentials: "include",
headers: { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }
});
if (!response.ok) throw new Error(`Network error when fetching from ${url}`);

const { data } = await response.json();
return data?.theme || (portal.isOsDarkThemeSet() ? portal.darkThemeClass : portal.lightThemeClass);
} catch (error) {
console.error(`${error.message}. Using OS theme preference.`);
return portal.isOsDarkThemeSet() ? portal.darkThemeClass : portal.lightThemeClass;
}
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@

#set ( $d = "$")
<script>
var portal = 'body';
var portal = {};
var needJQuery = true;
var secondJQuery = false;
if ( window.jQuery ) {
Expand Down

0 comments on commit 996a2f9

Please sign in to comment.