summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2022-06-19 12:10:57 +0200
committerYorhel <git@yorhel.nl>2022-06-19 12:10:59 +0200
commit43ef1a26d68f2b5dbc8b5ac3cc30e27b7bf89ca3 (patch)
tree1afcf240ee23a43511bb944ba57f50d26ad76781
parent888c7d968f44e86ffe2808cef67ed7e0e6145ad3 (diff)
Move & streamline VN release expanding settings
Problems with the old implementation were that the setting wasn't an intentional user action, so there were a LOT of preference writes that were reverted a minute or so later. Wasting both server and visitor time. For regular visitors, the localstorage-based approach also caused a reflow if their preferences didn't match the default. Downside of the new implementation is that regular visitors can't change their preference at all anymore, this now requires an account. :/
-rw-r--r--elm/Lib/Html.elm6
-rw-r--r--elm/User/Edit.elm14
-rw-r--r--elm/remember-details.js39
-rw-r--r--lib/VNWeb/Misc/SavePref.pm24
-rw-r--r--lib/VNWeb/User/Edit.pm13
-rw-r--r--lib/VNWeb/VN/Page.pm14
-rw-r--r--sql/schema.sql7
-rw-r--r--util/updates/2022-06-19-user-prefs-vnrel.sql31
8 files changed, 73 insertions, 75 deletions
diff --git a/elm/Lib/Html.elm b/elm/Lib/Html.elm
index 08648d55..fdb98823 100644
--- a/elm/Lib/Html.elm
+++ b/elm/Lib/Html.elm
@@ -29,6 +29,12 @@ onInputValidation msg = custom "input" <|
onInvalid : msg -> Attribute msg
onInvalid msg = on "invalid" (JD.succeed msg)
+onInputMultiple : (List String -> msg) -> Attribute msg
+onInputMultiple msg =
+ let dec = JD.at [ "target", "selectedOptions" ] <| JD.keyValuePairs <| JD.maybe (JD.field "value" JD.string)
+ f lst = msg (List.filterMap Tuple.second lst)
+ in on "input" (JD.map f dec)
+
-- Multi-<br> (ugly but oh, so, convenient)
br_ : Int -> Html m
br_ n = if n == 1 then br [] [] else span [] <| List.repeat n <| br [] []
diff --git a/elm/User/Edit.elm b/elm/User/Edit.elm
index f9b9f38c..447691c6 100644
--- a/elm/User/Edit.elm
+++ b/elm/User/Edit.elm
@@ -106,6 +106,9 @@ type PrefMsg
| TagsCont Bool
| TagsEro Bool
| TagsTech Bool
+ | VNRelLangs (List String)
+ | VNRelOLang Bool
+ | VNRelMTL Bool
| ProdRel Bool
| Skin String
| Css String
@@ -207,6 +210,9 @@ updatePrefs msg model =
TagsCont b -> { model | tags_cont = b }
TagsEro b -> { model | tags_ero = b }
TagsTech b -> { model | tags_tech = b }
+ VNRelLangs l->{ model | vnrel_langs = l }
+ VNRelOLang b->{ model | vnrel_olang = b }
+ VNRelMTL b -> { model | vnrel_mtl = b }
ProdRel b -> { model | prodrelexpand = b }
Skin n -> { model | skin = n }
Css n -> { model | customcss = n }
@@ -411,6 +417,14 @@ view model =
, label [] [ inputCheck "" m.tags_ero (Prefs << TagsEro ), text " Sexual content" ], br_ 1
, label [] [ inputCheck "" m.tags_tech (Prefs << TagsTech), text " Technical" ]
]
+ , formField "Releases"
+ [ text "Expand releases for the following languages by default", br_ 1
+ , select [ tabindex 10, multiple True, onInputMultiple (Prefs << VNRelLangs), style "height" "200px" ]
+ <| List.map (\(k,v) -> option [ value k, selected (List.member k m.vnrel_langs) ] [ text v ]) GT.languages
+ , br_ 1
+ , label [] [ inputCheck "" m.vnrel_olang (Prefs << VNRelOLang), text " Always expand original language" ], br_ 1
+ , label [] [ inputCheck "" m.vnrel_mtl (Prefs << VNRelMTL ), text " Expand machine translations" ]
+ ]
, tr [ class "newpart" ] [ td [ colspan 2 ] [ text "Theme" ] ]
, formField "skin::Skin" [ inputSelect "skin" m.skin (Prefs << Skin) [ style "width" "300px" ] GT.skins ]
, formField "css::Custom CSS" [ inputTextArea "css" m.customcss (Prefs << Css) ([ rows 5, cols 60 ] ++ GUE.valPrefsCustomcss) ]
diff --git a/elm/remember-details.js b/elm/remember-details.js
deleted file mode 100644
index 9d06bd55..00000000
--- a/elm/remember-details.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/* Simple script to remember the open/closed state of <details> elements.
- * Usage:
- *
- * <details data-remember-id=".."> .. </details>
- *
- * This does have the downside of causing a DOM reflow if the elements' default
- * state differs from the one stored by the user, and the preference is stored
- * in the users' browser rather than their account, so it doesn't transfer.
- */
-document.querySelectorAll('details[data-remember-id]').forEach(function(el) {
- var sid = 'remember-details-'+el.getAttribute('data-remember-id');
- el.addEventListener('toggle', function() {
- window.localStorage.setItem(sid, el.open ? '1' : '');
- });
- var val = window.localStorage.getItem(sid);
- if(val != null)
- el.open = val == '1' ? true : false;
-});
-
-
-/* Alternative to the above, for users who are logged in.
- * Usage:
- *
- * <details data-save-id=".."> .. </details>
- *
- * State changes will be saved with an AJAX call to /js/save-pref.
- * Preferences are already assumed to be loaded by server-side code, so this
- * approach does not cause a DOM reflow.
- */
-document.querySelectorAll('details[data-save-id]').forEach(function(el) {
- el.addEventListener('toggle', function() {
- var xhr = new XMLHttpRequest();
- xhr.open('POST', '/js/save-pref');
- xhr.setRequestHeader('Content-Type', 'application/json');
- var obj = {};
- obj[el.getAttribute('data-save-id')] = el.open;
- xhr.send(JSON.stringify(obj));
- });
-});
diff --git a/lib/VNWeb/Misc/SavePref.pm b/lib/VNWeb/Misc/SavePref.pm
deleted file mode 100644
index 542cc2a3..00000000
--- a/lib/VNWeb/Misc/SavePref.pm
+++ /dev/null
@@ -1,24 +0,0 @@
-package VNWeb::Misc::SavePref;
-
-use VNWeb::Prelude;
-
-my @vnlang_keys = (map +($_,"$_-mtl"), keys %LANGUAGE);
-
-TUWF::post qr{/js/save-pref} => sub {
- return tuwf->resDenied if !auth;
- my $prefs = tuwf->validate(json => {type => 'hash', unknown => 'pass'})->data;
-
- my %vnlang = map exists($prefs->{"vnlang-$_"}) ? ($_, $prefs->{"vnlang-$_"}) : (), @vnlang_keys;
- if(keys %vnlang) {
- my $v = tuwf->dbVali('SELECT vnlang FROM users_prefs WHERE id =', \auth->uid);
- $v = $v ? JSON::XS::decode_json($v) : {};
- for(keys %vnlang) {
- delete $v->{$_} if !defined $vnlang{$_};
- $v->{$_} = $vnlang{$_}?\1:\0 if defined $vnlang{$_};
- }
- $v = JSON::XS::encode_json($v);
- tuwf->dbExeci('UPDATE users_prefs SET vnlang =', \$v, 'WHERE id =', \auth->uid);
- }
-};
-
-1;
diff --git a/lib/VNWeb/User/Edit.pm b/lib/VNWeb/User/Edit.pm
index 98a09785..cf544179 100644
--- a/lib/VNWeb/User/Edit.pm
+++ b/lib/VNWeb/User/Edit.pm
@@ -41,6 +41,9 @@ my $FORM = {
tags_tech => { anybool => 1 },
prodrelexpand => { anybool => 1 },
spoilers => { uint => 1, range => [ 0, 2 ] },
+ vnrel_langs => { type => 'array', values => { enum => \%LANGUAGE }, sort => 'str', unique => 1 },
+ vnrel_olang => { anybool => 1 },
+ vnrel_mtl => { anybool => 1 },
skin => { enum => skins },
customcss => { required => 0, default => '', maxlength => 2000 },
@@ -89,13 +92,15 @@ TUWF::get qr{/$RE{uid}/edit}, sub {
$u->{prefs} = $u->{id} eq auth->uid || auth->permUsermod ?
tuwf->dbRowi(
- 'SELECT max_sexual, max_violence, traits_sexual, tags_all, tags_cont, tags_ero, tags_tech, prodrelexpand, spoilers, skin, customcss
- , nodistract_noads, nodistract_nofancy, support_enabled, uniname, pubskin_enabled, title_langs, alttitle_langs
+ 'SELECT max_sexual, max_violence, traits_sexual, tags_all, tags_cont, tags_ero, tags_tech, prodrelexpand
+ , spoilers, vnrel_langs::text[], vnrel_olang, vnrel_mtl, skin, customcss, title_langs, alttitle_langs
+ , nodistract_noads, nodistract_nofancy, support_enabled, uniname, pubskin_enabled
FROM users u JOIN users_prefs up ON up.id = u.id WHERE u.id =', \$u->{id}
) : undef;
if($u->{prefs}) {
$u->{prefs}{email} = _getmail $u->{id};
$u->{prefs}{skin} ||= config->{skin_default};
+ $u->{prefs}{vnrel_langs} ||= [ keys %LANGUAGE ];
$u->{prefs}{title_langs} = langpref_parse($u->{prefs}{title_langs}) // $DEFAULT_TITLE_LANGS;
$u->{prefs}{alttitle_langs} = langpref_parse($u->{prefs}{alttitle_langs}) // $DEFAULT_ALTTITLE_LANGS;
$u->{prefs}{traits} = tuwf->dbAlli('SELECT u.tid, t.name, g.name AS "group" FROM users_traits u JOIN traits t ON t.id = u.tid LEFT JOIN traits g ON g.id = t.group WHERE u.id =', \$u->{id}, 'ORDER BY g.order, t.name');
@@ -137,9 +142,11 @@ elm_api UserEdit => $FORM_OUT, $FORM_IN, sub {
$p->{alttitle_langs} = langpref_fmt $p->{alttitle_langs};
$p->{title_langs} = undef if $p->{title_langs} && ($p->{title_langs} eq langpref_fmt($DEFAULT_TITLE_LANGS) || $p->{title_langs} eq '[]');
$p->{alttitle_langs} = undef if $p->{alttitle_langs} && $p->{alttitle_langs} eq langpref_fmt $DEFAULT_ALTTITLE_LANGS;
+ $p->{vnrel_langs} = $p->{vnrel_langs}->@* == keys %LANGUAGE ? undef : '{'.join(',',$p->{vnrel_langs}->@*).'}';
$set{$_} = $p->{$_} for qw/nodistract_noads nodistract_nofancy support_enabled uniname pubskin_enabled/;
$setp{$_} = $p->{$_} for qw/
- max_sexual max_violence traits_sexual tags_all tags_cont tags_ero tags_tech prodrelexpand spoilers skin customcss title_langs alttitle_langs
+ max_sexual max_violence traits_sexual tags_all tags_cont tags_ero tags_tech prodrelexpand
+ vnrel_langs vnrel_olang vnrel_mtl spoilers skin customcss title_langs alttitle_langs
/;
tuwf->dbExeci('DELETE FROM users_traits WHERE id =', \$data->{id});
tuwf->dbExeci('INSERT INTO users_traits', { id => $data->{id}, tid => $_->{tid} }) for $p->{traits}->@*;
diff --git a/lib/VNWeb/VN/Page.pm b/lib/VNWeb/VN/Page.pm
index abe08476..4fb3a383 100644
--- a/lib/VNWeb/VN/Page.pm
+++ b/lib/VNWeb/VN/Page.pm
@@ -523,18 +523,18 @@ sub releases_ {
$langrel{$_} = min map $_->{released}, $lang{$_}->@* for keys %lang;
my @lang = sort { $langrel{$a} <=> $langrel{$b} || ($b eq $v->{olang}) cmp ($a eq $v->{olang}) || $a cmp $b } keys %lang;
- my $pref = +(auth && do {
- my $v = tuwf->dbVali('SELECT vnlang FROM users_prefs WHERE id =', \auth->uid);
- $v && JSON::XS::decode_json($v)
- }) || {};
+ my $pref = auth ? do {
+ my $v = tuwf->dbRowi('SELECT vnrel_langs::text[] AS langs, vnrel_olang AS olang, vnrel_mtl AS mtl FROM users_prefs WHERE id =', \auth->uid);
+ $v->{langs} = $v->{langs} ? { map +($_,1), $v->{langs}->@* } : \%LANGUAGE;
+ $v
+ } : { langs => \%LANGUAGE, olang => 1, mtl => 0 };
my sub lang_ {
my($lang) = @_;
my $ropt = { id => $lang, lang => $lang };
my $mtl = $langmtl{$lang};
- my $prefid = $lang.($mtl?'-mtl':'');
- my $open = $pref->{$prefid} // ($lang eq $v->{olang} || !$mtl);
- tag_ 'details', $open ? (open => 'open') : (), auth ? 'data-save-id' : 'data-remember-id', "vnlang-$prefid", sub {
+ my $open = ($pref->{olang} && $lang eq $v->{olang} && !$mtl) || ($pref->{langs}{$lang} && (!$mtl || $pref->{mtl}));
+ details_ open => $open?'open':undef, sub {
summary_ $mtl ? (class => 'mtl') : (), sub {
abbr_ class => "icons lang $lang".($mtl?' mtl':''), title => $LANGUAGE{$lang}, '';
txt_ $LANGUAGE{$lang};
diff --git a/sql/schema.sql b/sql/schema.sql
index d215546e..40b35603 100644
--- a/sql/schema.sql
+++ b/sql/schema.sql
@@ -1057,10 +1057,13 @@ CREATE TABLE users_prefs (
ulist_votes jsonb,
ulist_vnlist jsonb,
ulist_wish jsonb,
- vnlang jsonb, -- '$lang(-mtl)?' => true/false, which languages to expand/collapse on VN pages
+ vnlang jsonb, -- Deprecated, replaced by vnrel_x. '$lang(-mtl)?' => true/false, which languages to expand/collapse on VN pages
title_langs jsonb,
alttitle_langs jsonb,
- prodrelexpand boolean NOT NULL DEFAULT true
+ prodrelexpand boolean NOT NULL DEFAULT true,
+ vnrel_langs language[], -- NULL meaning "show all languages"
+ vnrel_olang boolean NOT NULL DEFAULT true,
+ vnrel_mtl boolean NOT NULL DEFAULT false
);
-- Additional fields for the 'users' table, but with some protected columns.
diff --git a/util/updates/2022-06-19-user-prefs-vnrel.sql b/util/updates/2022-06-19-user-prefs-vnrel.sql
new file mode 100644
index 00000000..f9321b93
--- /dev/null
+++ b/util/updates/2022-06-19-user-prefs-vnrel.sql
@@ -0,0 +1,31 @@
+ALTER TABLE users_prefs ADD COLUMN vnrel_langs language[],
+ ADD COLUMN vnrel_olang boolean NOT NULL DEFAULT true,
+ ADD COLUMN vnrel_mtl boolean NOT NULL DEFAULT false;
+
+-- Attempt to infer vnrel_langs and vnrel_mtl from the old 'vnlang' column.
+BEGIN;
+
+CREATE OR REPLACE FUNCTION vnlang_to_langs(vnlang jsonb) RETURNS language[] AS $$
+DECLARE
+ ret language[];
+ del language;
+BEGIN
+ ret := enum_range(null::language);
+ FOR del IN SELECT key::language FROM jsonb_each(vnlang) x WHERE key NOT LIKE '%-mtl' AND value = 'false'
+ LOOP
+ ret := array_remove(ret, del);
+ END LOOP;
+ RETURN CASE WHEN array_length(ret,1) = array_length(enum_range(null::language),1) THEN NULL ELSE RET END;
+END$$ LANGUAGE plpgsql;
+
+WITH p(id,langs,mtl) AS (
+ SELECT id, vnlang_to_langs(vnlang), vnlang->'en-mtl' is not distinct from 'true'
+ FROM users_prefs WHERE vnlang IS NOT NULL
+) UPDATE users_prefs
+ SET vnrel_langs = langs, vnrel_mtl = mtl
+ FROM p
+ WHERE p.id = users_prefs.id AND (langs IS NOT NULL OR mtl);
+
+DROP FUNCTION vnlang_to_langs(jsonb);
+
+COMMIT;