mirror of
https://github.com/iv-org/invidious.git
synced 2025-05-20 07:10:43 -04:00
Add prefers-color-scheme support (#601)
* Add prefers-color-scheme support This should fix <https://github.com/omarroth/invidious/issues/559>. The cookie storage format has been changed from boolean ("true"/"false") to tri-state ("dark"/"light"/""), so that users without a cookie set will get dark mode if they have enabled the dark theme in their operating system. The code for handling the cookie state, along with the user's operating system theme, has been factored out into a new function `update_mode`, which is called both at window load and at the "storage" event listener, because the "storage" event listener is only trigerred when a change is made to the localStorage from another tab/window (for more info - see <https://stackoverflow.com/a/4679754>).
This commit is contained in:
parent
19eceb4ecc
commit
f54fbd057e
24 changed files with 215 additions and 84 deletions
|
@ -267,8 +267,7 @@ before_all do |env|
|
|||
end
|
||||
end
|
||||
|
||||
dark_mode = env.params.query["dark_mode"]? || preferences.dark_mode.to_s
|
||||
dark_mode = dark_mode == "true"
|
||||
dark_mode = convert_theme(env.params.query["dark_mode"]?) || preferences.dark_mode.to_s
|
||||
|
||||
thin_mode = env.params.query["thin_mode"]? || preferences.thin_mode.to_s
|
||||
thin_mode = thin_mode == "true"
|
||||
|
@ -1528,8 +1527,7 @@ post "/preferences" do |env|
|
|||
locale ||= CONFIG.default_user_preferences.locale
|
||||
|
||||
dark_mode = env.params.body["dark_mode"]?.try &.as(String)
|
||||
dark_mode ||= "off"
|
||||
dark_mode = dark_mode == "on"
|
||||
dark_mode ||= CONFIG.default_user_preferences.dark_mode
|
||||
|
||||
thin_mode = env.params.body["thin_mode"]?.try &.as(String)
|
||||
thin_mode ||= "off"
|
||||
|
@ -1553,6 +1551,7 @@ post "/preferences" do |env|
|
|||
notifications_only ||= "off"
|
||||
notifications_only = notifications_only == "on"
|
||||
|
||||
# Convert to JSON and back again to take advantage of converters used for compatability
|
||||
preferences = Preferences.from_json({
|
||||
annotations: annotations,
|
||||
annotations_subscribed: annotations_subscribed,
|
||||
|
@ -1648,12 +1647,27 @@ get "/toggle_theme" do |env|
|
|||
if user = env.get? "user"
|
||||
user = user.as(User)
|
||||
preferences = user.preferences
|
||||
preferences.dark_mode = !preferences.dark_mode
|
||||
|
||||
PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences.to_json, user.email)
|
||||
case preferences.dark_mode
|
||||
when "dark"
|
||||
preferences.dark_mode = "light"
|
||||
else
|
||||
preferences.dark_mode = "dark"
|
||||
end
|
||||
|
||||
preferences = preferences.to_json
|
||||
|
||||
PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences, user.email)
|
||||
else
|
||||
preferences = env.get("preferences").as(Preferences)
|
||||
preferences.dark_mode = !preferences.dark_mode
|
||||
|
||||
case preferences.dark_mode
|
||||
when "dark"
|
||||
preferences.dark_mode = "light"
|
||||
else
|
||||
preferences.dark_mode = "dark"
|
||||
end
|
||||
|
||||
preferences = preferences.to_json
|
||||
|
||||
if Kemal.config.ssl || config.https_only
|
||||
|
@ -2026,7 +2040,7 @@ post "/data_control" do |env|
|
|||
env.response.puts %(<meta http-equiv="refresh" content="0; url=#{referer}">)
|
||||
env.response.puts %(<link rel="stylesheet" href="/css/ionicons.min.css?v=#{ASSET_COMMIT}">)
|
||||
env.response.puts %(<link rel="stylesheet" href="/css/default.css?v=#{ASSET_COMMIT}">)
|
||||
if env.get("preferences").as(Preferences).dark_mode
|
||||
if env.get("preferences").as(Preferences).dark_mode == "dark"
|
||||
env.response.puts %(<link rel="stylesheet" href="/css/darktheme.css?v=#{ASSET_COMMIT}">)
|
||||
else
|
||||
env.response.puts %(<link rel="stylesheet" href="/css/lighttheme.css?v=#{ASSET_COMMIT}">)
|
||||
|
|
|
@ -24,6 +24,27 @@ end
|
|||
|
||||
struct ConfigPreferences
|
||||
module StringToArray
|
||||
def self.to_json(value : Array(String), json : JSON::Builder)
|
||||
json.array do
|
||||
value.each do |element|
|
||||
json.string element
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.from_json(value : JSON::PullParser) : Array(String)
|
||||
begin
|
||||
result = [] of String
|
||||
value.read_array do
|
||||
result << HTML.escape(value.read_string[0, 100])
|
||||
end
|
||||
rescue ex
|
||||
result = [HTML.escape(value.read_string[0, 100]), ""]
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder)
|
||||
yaml.sequence do
|
||||
value.each do |element|
|
||||
|
@ -44,11 +65,11 @@ struct ConfigPreferences
|
|||
node.raise "Expected scalar, not #{item.class}"
|
||||
end
|
||||
|
||||
result << item.value
|
||||
result << HTML.escape(item.value[0, 100])
|
||||
end
|
||||
rescue ex
|
||||
if node.is_a?(YAML::Nodes::Scalar)
|
||||
result = [node.value, ""]
|
||||
result = [HTML.escape(node.value[0, 100]), ""]
|
||||
else
|
||||
result = ["", ""]
|
||||
end
|
||||
|
@ -58,6 +79,53 @@ struct ConfigPreferences
|
|||
end
|
||||
end
|
||||
|
||||
module BoolToString
|
||||
def self.to_json(value : String, json : JSON::Builder)
|
||||
json.string value
|
||||
end
|
||||
|
||||
def self.from_json(value : JSON::PullParser) : String
|
||||
begin
|
||||
result = value.read_string
|
||||
|
||||
if result.empty?
|
||||
CONFIG.default_user_preferences.dark_mode
|
||||
else
|
||||
result
|
||||
end
|
||||
rescue ex
|
||||
result = value.read_bool
|
||||
|
||||
if result
|
||||
"dark"
|
||||
else
|
||||
"light"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.to_yaml(value : String, yaml : YAML::Nodes::Builder)
|
||||
yaml.scalar value
|
||||
end
|
||||
|
||||
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : String
|
||||
unless node.is_a?(YAML::Nodes::Scalar)
|
||||
node.raise "Expected sequence, not #{node.class}"
|
||||
end
|
||||
|
||||
case node.value
|
||||
when "true"
|
||||
"dark"
|
||||
when "false"
|
||||
"light"
|
||||
when ""
|
||||
CONFIG.default_user_preferences.dark_mode
|
||||
else
|
||||
node.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
yaml_mapping({
|
||||
annotations: {type: Bool, default: false},
|
||||
annotations_subscribed: {type: Bool, default: false},
|
||||
|
@ -66,7 +134,7 @@ struct ConfigPreferences
|
|||
comments: {type: Array(String), default: ["youtube", ""], converter: StringToArray},
|
||||
continue: {type: Bool, default: false},
|
||||
continue_autoplay: {type: Bool, default: true},
|
||||
dark_mode: {type: Bool, default: false},
|
||||
dark_mode: {type: String, default: "", converter: BoolToString},
|
||||
latest_only: {type: Bool, default: false},
|
||||
listen: {type: Bool, default: false},
|
||||
local: {type: Bool, default: false},
|
||||
|
|
|
@ -4,7 +4,7 @@ def Object.from_json(string_or_io, default) : self
|
|||
new parser, default
|
||||
end
|
||||
|
||||
# Adds configurable 'default' to
|
||||
# Adds configurable 'default'
|
||||
macro patched_json_mapping(_properties_, strict = false)
|
||||
{% for key, value in _properties_ %}
|
||||
{% _properties_[key] = {type: value} unless value.is_a?(HashLiteral) || value.is_a?(NamedTupleLiteral) %}
|
||||
|
|
|
@ -356,3 +356,16 @@ def parse_range(range)
|
|||
|
||||
return 0_i64, nil
|
||||
end
|
||||
|
||||
def convert_theme(theme)
|
||||
case theme
|
||||
when "true"
|
||||
"dark"
|
||||
when "false"
|
||||
"light"
|
||||
when "", nil
|
||||
nil
|
||||
else
|
||||
theme
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,62 +31,6 @@ struct User
|
|||
end
|
||||
|
||||
struct Preferences
|
||||
module StringToArray
|
||||
def self.to_json(value : Array(String), json : JSON::Builder)
|
||||
json.array do
|
||||
value.each do |element|
|
||||
json.string element
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.from_json(value : JSON::PullParser) : Array(String)
|
||||
begin
|
||||
result = [] of String
|
||||
value.read_array do
|
||||
result << HTML.escape(value.read_string[0, 100])
|
||||
end
|
||||
rescue ex
|
||||
result = [HTML.escape(value.read_string[0, 100]), ""]
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder)
|
||||
yaml.sequence do
|
||||
value.each do |element|
|
||||
yaml.scalar element
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Array(String)
|
||||
begin
|
||||
unless node.is_a?(YAML::Nodes::Sequence)
|
||||
node.raise "Expected sequence, not #{node.class}"
|
||||
end
|
||||
|
||||
result = [] of String
|
||||
node.nodes.each do |item|
|
||||
unless item.is_a?(YAML::Nodes::Scalar)
|
||||
node.raise "Expected scalar, not #{item.class}"
|
||||
end
|
||||
|
||||
result << HTML.escape(item.value[0, 100])
|
||||
end
|
||||
rescue ex
|
||||
if node.is_a?(YAML::Nodes::Scalar)
|
||||
result = [HTML.escape(node.value[0, 100]), ""]
|
||||
else
|
||||
result = ["", ""]
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
module ProcessString
|
||||
def self.to_json(value : String, json : JSON::Builder)
|
||||
json.string value
|
||||
|
@ -127,11 +71,11 @@ struct Preferences
|
|||
annotations: {type: Bool, default: CONFIG.default_user_preferences.annotations},
|
||||
annotations_subscribed: {type: Bool, default: CONFIG.default_user_preferences.annotations_subscribed},
|
||||
autoplay: {type: Bool, default: CONFIG.default_user_preferences.autoplay},
|
||||
captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, converter: StringToArray},
|
||||
comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: StringToArray},
|
||||
captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, converter: ConfigPreferences::StringToArray},
|
||||
comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: ConfigPreferences::StringToArray},
|
||||
continue: {type: Bool, default: CONFIG.default_user_preferences.continue},
|
||||
continue_autoplay: {type: Bool, default: CONFIG.default_user_preferences.continue_autoplay},
|
||||
dark_mode: {type: Bool, default: CONFIG.default_user_preferences.dark_mode},
|
||||
dark_mode: {type: String, default: CONFIG.default_user_preferences.dark_mode, converter: ConfigPreferences::BoolToString},
|
||||
latest_only: {type: Bool, default: CONFIG.default_user_preferences.latest_only},
|
||||
listen: {type: Bool, default: CONFIG.default_user_preferences.listen},
|
||||
local: {type: Bool, default: CONFIG.default_user_preferences.local},
|
||||
|
|
|
@ -122,8 +122,12 @@ function update_value(element) {
|
|||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="dark_mode"><%= translate(locale, "Dark mode: ") %></label>
|
||||
<input name="dark_mode" id="dark_mode" type="checkbox" <% if preferences.dark_mode %>checked<% end %>>
|
||||
<label for="dark_mode"><%= translate(locale, "Theme: ") %></label>
|
||||
<select name="dark_mode" id="dark_mode">
|
||||
<% {"", "light", "dark"}.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.dark_mode == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
|
|
|
@ -18,13 +18,14 @@
|
|||
<link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>">
|
||||
<link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>">
|
||||
<link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>">
|
||||
<link rel="stylesheet" href="/css/darktheme.css?v=<%= ASSET_COMMIT %>" id="dark_theme" <% if !env.get("preferences").as(Preferences).dark_mode %>media="none"<% end %>>
|
||||
<link rel="stylesheet" href="/css/lighttheme.css?v=<%= ASSET_COMMIT %>" id="light_theme" <% if env.get("preferences").as(Preferences).dark_mode %>media="none"<% end %>>
|
||||
<link rel="stylesheet" href="/css/darktheme.css?v=<%= ASSET_COMMIT %>" id="dark_theme" <% if env.get("preferences").as(Preferences).dark_mode != "dark" %>media="none"<% end %>>
|
||||
<link rel="stylesheet" href="/css/lighttheme.css?v=<%= ASSET_COMMIT %>" id="light_theme" <% if env.get("preferences").as(Preferences).dark_mode == "dark" %>media="none"<% end %>>
|
||||
</head>
|
||||
|
||||
<% locale = LOCALES[env.get("preferences").as(Preferences).locale]? %>
|
||||
|
||||
<body>
|
||||
<span style="display:none" id="dark_mode_pref"><%= env.get("preferences").as(Preferences).dark_mode %></span>
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-2-24"></div>
|
||||
<div class="pure-u-1 pure-u-md-20-24">
|
||||
|
@ -43,7 +44,7 @@
|
|||
<% if env.get? "user" %>
|
||||
<div class="pure-u-1-4">
|
||||
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
||||
<% if env.get("preferences").as(Preferences).dark_mode %>
|
||||
<% if env.get("preferences").as(Preferences).dark_mode == "dark" %>
|
||||
<i class="icon ion-ios-sunny"></i>
|
||||
<% else %>
|
||||
<i class="icon ion-ios-moon"></i>
|
||||
|
@ -76,7 +77,7 @@
|
|||
<% else %>
|
||||
<div class="pure-u-1-3">
|
||||
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
||||
<% if env.get("preferences").as(Preferences).dark_mode %>
|
||||
<% if env.get("preferences").as(Preferences).dark_mode == "dark" %>
|
||||
<i class="icon ion-ios-sunny"></i>
|
||||
<% else %>
|
||||
<i class="icon ion-ios-moon"></i>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue