Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add settings to rustdoc to use the system theme #77809

Merged
merged 4 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 85 additions & 17 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,8 @@ impl FormatRenderer for Context {
settings(
self.shared.static_root_path.as_deref().unwrap_or("./"),
&self.shared.resource_suffix,
),
&self.shared.style_files,
)?,
&style_files,
);
self.shared.fs.write(&settings_file, v.as_bytes())?;
Expand Down Expand Up @@ -810,6 +811,7 @@ themePicker.onblur = handleThemeButtonsBlur;
but.textContent = item;
but.onclick = function(el) {{
switchTheme(currentTheme, mainTheme, item, true);
useSystemTheme(false);
}};
but.onblur = handleThemeButtonsBlur;
themes.appendChild(but);
Expand Down Expand Up @@ -1343,22 +1345,35 @@ impl AllTypes {

#[derive(Debug)]
enum Setting {
Section { description: &'static str, sub_settings: Vec<Setting> },
Entry { js_data_name: &'static str, description: &'static str, default_value: bool },
Section {
description: &'static str,
sub_settings: Vec<Setting>,
},
Toggle {
js_data_name: &'static str,
description: &'static str,
default_value: bool,
},
Select {
js_data_name: &'static str,
description: &'static str,
default_value: &'static str,
options: Vec<(String, String)>,
},
}

impl Setting {
fn display(&self) -> String {
fn display(&self, root_path: &str, suffix: &str) -> String {
match *self {
Setting::Section { ref description, ref sub_settings } => format!(
Setting::Section { description, ref sub_settings } => format!(
"<div class='setting-line'>\
<div class='title'>{}</div>\
<div class='sub-settings'>{}</div>
</div>",
description,
sub_settings.iter().map(|s| s.display()).collect::<String>()
sub_settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>()
),
Setting::Entry { ref js_data_name, ref description, ref default_value } => format!(
Setting::Toggle { js_data_name, description, default_value } => format!(
"<div class='setting-line'>\
<label class='toggle'>\
<input type='checkbox' id='{}' {}>\
Expand All @@ -1367,16 +1382,38 @@ impl Setting {
<div>{}</div>\
</div>",
js_data_name,
if *default_value { " checked" } else { "" },
if default_value { " checked" } else { "" },
description,
),
Setting::Select { js_data_name, description, default_value, ref options } => format!(
"<div class=\"setting-line\">\
<div>{}</div>\
<label class=\"select-wrapper\">\
<select id=\"{}\" autocomplete=\"off\">{}</select>\
<img src=\"{}down-arrow{}.svg\" alt=\"Select item\">\
</label>\
</div>",
description,
js_data_name,
options
.iter()
.map(|opt| format!(
"<option value=\"{}\" {}>{}</option>",
opt.0,
if &opt.0 == default_value { "selected" } else { "" },
opt.1,
))
.collect::<String>(),
root_path,
suffix,
),
}
}
}

impl From<(&'static str, &'static str, bool)> for Setting {
fn from(values: (&'static str, &'static str, bool)) -> Setting {
Setting::Entry { js_data_name: values.0, description: values.1, default_value: values.2 }
Setting::Toggle { js_data_name: values.0, description: values.1, default_value: values.2 }
}
}

Expand All @@ -1389,9 +1426,39 @@ impl<T: Into<Setting>> From<(&'static str, Vec<T>)> for Setting {
}
}

fn settings(root_path: &str, suffix: &str) -> String {
fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result<String, Error> {
let theme_names: Vec<(String, String)> = themes
.iter()
.map(|entry| {
let theme =
try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path)
.to_string();

Ok((theme.clone(), theme))
})
.collect::<Result<_, Error>>()?;

// (id, explanation, default value)
let settings: &[Setting] = &[
(
"Theme preferences",
vec![
Setting::from(("use-system-theme", "Use system theme", true)),
Setting::Select {
js_data_name: "preferred-dark-theme",
description: "Preferred dark theme",
default_value: "dark",
options: theme_names.clone(),
},
Setting::Select {
js_data_name: "preferred-light-theme",
description: "Preferred light theme",
default_value: "light",
options: theme_names,
},
],
)
.into(),
(
"Auto-hide item declarations",
vec![
Expand All @@ -1413,16 +1480,17 @@ fn settings(root_path: &str, suffix: &str) -> String {
("line-numbers", "Show line numbers on code examples", false).into(),
("disable-shortcuts", "Disable keyboard shortcuts", false).into(),
];
format!(

Ok(format!(
"<h1 class='fqn'>\
<span class='in-band'>Rustdoc settings</span>\
</h1>\
<div class='settings'>{}</div>\
<script src='{}settings{}.js'></script>",
settings.iter().map(|s| s.display()).collect::<String>(),
<span class='in-band'>Rustdoc settings</span>\
</h1>\
<div class='settings'>{}</div>\
<script src='{}settings{}.js'></script>",
settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>(),
root_path,
suffix
)
))
}

impl Context {
Expand Down
33 changes: 32 additions & 1 deletion src/librustdoc/html/static/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
}

.setting-line > div {
max-width: calc(100% - 74px);
nasso marked this conversation as resolved.
Show resolved Hide resolved
display: inline-block;
vertical-align: top;
font-size: 17px;
Expand All @@ -30,6 +29,38 @@
display: none;
}

.select-wrapper {
float: right;
position: relative;
nasso marked this conversation as resolved.
Show resolved Hide resolved
height: 27px;
min-width: 25%;
}

.select-wrapper select {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
background: none;
border: 2px solid #ccc;
padding-right: 28px;
width: 100%;
}

.select-wrapper img {
pointer-events: none;
position: absolute;
right: 0;
bottom: 0;
background: #ccc;
height: 100%;
width: 28px;
padding: 0px 4px;
}

.select-wrapper select option {
color: initial;
}

.slider {
position: absolute;
cursor: pointer;
Expand Down
58 changes: 42 additions & 16 deletions src/librustdoc/html/static/settings.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,56 @@
// Local js definitions:
/* global getCurrentValue, updateLocalStorage */
/* global getCurrentValue, updateLocalStorage, updateSystemTheme */

(function () {
function changeSetting(settingName, isEnabled) {
updateLocalStorage('rustdoc-' + settingName, isEnabled);
function changeSetting(settingName, value) {
updateLocalStorage("rustdoc-" + settingName, value);

switch (settingName) {
case "preferred-dark-theme":
case "preferred-light-theme":
case "use-system-theme":
updateSystemTheme();
break;
}
}

function getSettingValue(settingName) {
return getCurrentValue('rustdoc-' + settingName);
return getCurrentValue("rustdoc-" + settingName);
}

function setEvents() {
var elems = document.getElementsByClassName("slider");
if (!elems || elems.length === 0) {
return;
var elems = {
toggles: document.getElementsByClassName("slider"),
selects: document.getElementsByClassName("select-wrapper")
};
var i;

if (elems.toggles && elems.toggles.length > 0) {
for (i = 0; i < elems.toggles.length; ++i) {
var toggle = elems.toggles[i].previousElementSibling;
var settingId = toggle.id;
var settingValue = getSettingValue(settingId);
if (settingValue !== null) {
toggle.checked = settingValue === "true";
}
toggle.onchange = function() {
changeSetting(this.id, this.checked);
};
}
}
for (var i = 0; i < elems.length; ++i) {
var toggle = elems[i].previousElementSibling;
var settingId = toggle.id;
var settingValue = getSettingValue(settingId);
if (settingValue !== null) {
toggle.checked = settingValue === "true";

if (elems.selects && elems.selects.length > 0) {
for (i = 0; i < elems.selects.length; ++i) {
var select = elems.selects[i].getElementsByTagName("select")[0];
var settingId = select.id;
var settingValue = getSettingValue(settingId);
if (settingValue !== null) {
select.value = settingValue;
}
select.onchange = function() {
changeSetting(this.id, this.value);
};
}
toggle.onchange = function() {
changeSetting(this.id, this.checked);
};
}
}

Expand Down
89 changes: 81 additions & 8 deletions src/librustdoc/html/static/storage.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// From rust:
/* global resourcesSuffix */

var darkThemes = ["dark", "ayu"];
var currentTheme = document.getElementById("themeStyle");
var mainTheme = document.getElementById("mainThemeStyle");
var localStoredTheme = getCurrentValue("rustdoc-theme");

var savedHref = [];

Expand Down Expand Up @@ -110,19 +112,90 @@ function switchTheme(styleElem, mainStyleElem, newTheme, saveTheme) {
});
if (found === true) {
styleElem.href = newHref;
// If this new value comes from a system setting or from the previously saved theme, no
// need to save it.
// If this new value comes from a system setting or from the previously
// saved theme, no need to save it.
if (saveTheme === true) {
updateLocalStorage("rustdoc-theme", newTheme);
}
}
}

function getSystemValue() {
var property = getComputedStyle(document.documentElement).getPropertyValue('content');
return property.replace(/[\"\']/g, "");
function useSystemTheme(value) {
if (value === undefined) {
value = true;
}

updateLocalStorage("rustdoc-use-system-theme", value);

// update the toggle if we're on the settings page
var toggle = document.getElementById("use-system-theme");
if (toggle && toggle instanceof HTMLInputElement) {
toggle.checked = value;
}
}

switchTheme(currentTheme, mainTheme,
getCurrentValue("rustdoc-theme") || getSystemValue() || "light",
false);
var updateSystemTheme = (function() {
if (!window.matchMedia) {
// fallback to the CSS computed value
return function() {
let cssTheme = getComputedStyle(document.documentElement)
.getPropertyValue('content');

switchTheme(
currentTheme,
mainTheme,
JSON.parse(cssTheme) || light,
true
);
};
}

// only listen to (prefers-color-scheme: dark) because light is the default
var mql = window.matchMedia("(prefers-color-scheme: dark)");

function handlePreferenceChange(mql) {
// maybe the user has disabled the setting in the meantime!
if (getCurrentValue("rustdoc-use-system-theme") !== "false") {
var lightTheme = getCurrentValue("rustdoc-preferred-light-theme") || "light";
var darkTheme = getCurrentValue("rustdoc-preferred-dark-theme") || "dark";

if (mql.matches) {
// prefers a dark theme
switchTheme(currentTheme, mainTheme, darkTheme, true);
} else {
// prefers a light theme, or has no preference
switchTheme(currentTheme, mainTheme, lightTheme, true);
}

// note: we save the theme so that it doesn't suddenly change when
// the user disables "use-system-theme" and reloads the page or
// navigates to another page
}
}

mql.addListener(handlePreferenceChange);

return function() {
handlePreferenceChange(mql);
};
})();

if (getCurrentValue("rustdoc-use-system-theme") !== "false" && window.matchMedia) {
// update the preferred dark theme if the user is already using a dark theme
// See https://github.com/rust-lang/rust/pull/77809#issuecomment-707875732
if (getCurrentValue("rustdoc-use-system-theme") === null
&& getCurrentValue("rustdoc-preferred-dark-theme") === null
&& darkThemes.indexOf(localStoredTheme) >= 0) {
updateLocalStorage("rustdoc-preferred-dark-theme", localStoredTheme);
}

// call the function to initialize the theme at least once!
updateSystemTheme();
} else {
switchTheme(
currentTheme,
mainTheme,
getCurrentValue("rustdoc-theme") || "light",
false
);
}