diff options
author | Yorhel <git@yorhel.nl> | 2020-07-15 13:40:59 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2020-07-15 13:41:02 +0200 |
commit | 8d4d759256316de49710796152488ba1da09f731 (patch) | |
tree | 93346ca4c2916cf360cc2366040b266fbc011b64 | |
parent | 1690494ea55ae3e1e4fa002d5fd8bd1999c431a5 (diff) |
UList::Export: Add initial list export functionality
-rw-r--r-- | data/style.css | 1 | ||||
-rw-r--r-- | elm/UList/ManageLabels.js | 8 | ||||
-rw-r--r-- | elm/UList/SaveDefault.js | 7 | ||||
-rw-r--r-- | elm/UList/actiontabs.js | 17 | ||||
-rw-r--r-- | lib/VNWeb/ULists/Export.pm | 101 | ||||
-rw-r--r-- | lib/VNWeb/ULists/List.pm | 10 |
6 files changed, 129 insertions, 15 deletions
diff --git a/data/style.css b/data/style.css index 148994f3..2c482e8c 100644 --- a/data/style.css +++ b/data/style.css @@ -807,6 +807,7 @@ div.votelist td.tc2 { width: 50px; text-align: right; padding-right: 10px } .managelabels tfoot div { float: right; text-align: right } .savedefault { width: 600px; margin: 10px auto } +.exportlist { width: 600px; margin: 10px auto } .ulist .tc1 { white-space: nowrap; width: 70px } .ulist .tc1 label { cursor: pointer } diff --git a/elm/UList/ManageLabels.js b/elm/UList/ManageLabels.js index f9f8c68b..3ff2db61 100644 --- a/elm/UList/ManageLabels.js +++ b/elm/UList/ManageLabels.js @@ -1,11 +1,3 @@ -document.querySelectorAll('#managelabels').forEach(function(b) { - b.onclick = function() { - document.querySelectorAll('.managelabels').forEach(function(e) { e.classList.toggle('hidden') }) - document.querySelectorAll('.savedefault').forEach(function(e) { e.classList.add('hidden') }) - }; - return false; -}); - wrap_elm_init('UList.ManageLabels', function(init, opt) { opt.flags = { uid: pageVars.uid, labels: pageVars.labels }; init(opt); diff --git a/elm/UList/SaveDefault.js b/elm/UList/SaveDefault.js deleted file mode 100644 index a253680f..00000000 --- a/elm/UList/SaveDefault.js +++ /dev/null @@ -1,7 +0,0 @@ -document.querySelectorAll('#savedefault').forEach(function(b) { - b.onclick = function() { - document.querySelectorAll('.savedefault').forEach(function(e) { e.classList.toggle('hidden') }) - document.querySelectorAll('.managelabels').forEach(function(e) { e.classList.add('hidden') }) - }; - return false; -}); diff --git a/elm/UList/actiontabs.js b/elm/UList/actiontabs.js new file mode 100644 index 00000000..0ae2b7f9 --- /dev/null +++ b/elm/UList/actiontabs.js @@ -0,0 +1,17 @@ +var buttons = ['managelabels', 'savedefault', 'exportlist']; + +buttons.forEach(function(but) { + document.querySelectorAll('#'+but).forEach(function(b) { + b.onclick = function() { + buttons.forEach(function(but2) { + document.querySelectorAll('.'+but2).forEach(function(e) { + if(but == but2) + e.classList.toggle('hidden'); + else + e.classList.add('hidden') + }) + }) + return false; + } + }) +}) diff --git a/lib/VNWeb/ULists/Export.pm b/lib/VNWeb/ULists/Export.pm new file mode 100644 index 00000000..acfbcccf --- /dev/null +++ b/lib/VNWeb/ULists/Export.pm @@ -0,0 +1,101 @@ +package VNWeb::ULists::Export; + +use TUWF::XML ':xml'; +use VNWeb::Prelude; +use VNWeb::ULists::Lib; + +# XXX: Reading someone's entire list into memory (multiple times even) is not +# the most efficient way to implement an export function. Might want to switch +# to an async background process for this to reduce the footprint of web +# workers. + +sub data { + my($uid) = @_; + + # We'd like ISO7601/RFC3339 timestamps in UTC with accuracy to the second. + my sub tz { sql 'to_char(', $_[0], ' at time zone \'utc\',', \'YYYY-MM-DD"T"HH24:MM:SS"Z"', ') as', $_[1] } + + my $d = { + 'export-date' => tuwf->dbVali(select => tz('NOW()', 'now')), + user => tuwf->dbRowi('SELECT id, username as name FROM users WHERE id =', \$uid), + labels => tuwf->dbAlli('SELECT id, label, private FROM ulist_labels WHERE uid =', \$uid, 'ORDER BY id'), + vns => tuwf->dbAlli(' + SELECT v.id, v.title, v.original, uv.vote, uv.started, uv.finished, uv.notes + , ', sql_comma(tz('uv.added', 'added'), tz('uv.lastmod', 'lastmod'), tz('uv.vote_date', 'vote_date')), ' + FROM ulist_vns uv + JOIN vn v ON v.id = uv.vid + WHERE uv.uid =', \$uid, ' + ORDER BY v.title') + }; + enrich labels => id => vid => sub { sql ' + SELECT uvl.vid, ul.id, ul.label, ul.private + FROM ulist_vns_labels uvl + JOIN ulist_labels ul ON ul.id = uvl.lbl + WHERE ul.uid =', \$uid, 'AND uvl.uid =', \$uid, ' + ORDER BY lbl' + }, $d->{vns}; + enrich releases => id => vid => sub { sql ' + SELECT rv.vid, r.id, r.title, r.original, r.released, rl.status, ', tz('rl.added', 'added'), ' + FROM rlists rl + JOIN releases r ON r.id = rl.rid + JOIN releases_vn rv ON rv.id = rl.rid + WHERE rl.uid =', \$uid, ' + ORDER BY r.released, r.id' + }, $d->{vns}; + $d +} + + +sub filename { + my($d, $ext) = @_; + my $date = $d->{'export-date'} =~ s/[-TZ:]//rg; + "vndb-list-export-$d->{user}{name}-$date.$ext" +} + + +TUWF::get qr{/$RE{uid}/list-export/xml}, sub { + my $uid = tuwf->capture('id'); + return tuwf->resDenied if !ulists_own $uid; + my $d = data $uid; + return tuwf->resNotFound if !$d->{user}{id}; + + tuwf->resHeader('Content-Disposition', sprintf 'attachment; filename="%s"', filename $d, 'xml'); + tuwf->resHeader('Content-Type', 'application/xml; charset=UTF-8'); + + my $fd = tuwf->resFd; + TUWF::XML->new( + write => sub { print $fd $_ for @_ }, + pretty => 2, + default => 1, + ); + xml; + tag 'vndb-export' => version => '1.0', date => $d->{'export-date'}, sub { + tag user => sub { + tag name => $d->{user}{name}; + tag url => config->{url}.'/u'.$d->{user}{id}; + }; + tag labels => sub { + tag label => id => $_->{id}, label => $_->{label}, private => $_->{private}?'true':'false', undef for $d->{labels}->@*; + }; + tag vns => sub { + tag vn => id => "v$_->{id}", private => grep(!$_->{private}, $_->{labels}->@*)?'false':'true', sub { + tag title => length($_->{original}) ? (original => $_->{original}) : (), $_->{title}; + tag label => id => $_->{id}, label => $_->{label}, undef for $_->{labels}->@*; + tag added => $_->{added}; + tag modified => $_->{lastmod} if $_->{added} ne $_->{lastmod}; + tag vote => timestamp => $_->{vote_date}, fmtvote $_->{vote} if $_->{vote}; + tag started => $_->{started} if $_->{started}; + tag finished => $_->{finished} if $_->{finished}; + tag notes => $_->{notes} if length $_->{notes}; + tag release => id => "r$_->{id}", sub { + tag title => length($_->{original}) ? (original => $_->{original}) : (), $_->{title}; + tag 'release-date' => rdate $_->{released}; + tag status => $RLIST_STATUS{$_->{status}}; + tag added => $_->{added}; + } for $_->{releases}->@*; + } for $d->{vns}->@*; + }; + }; +}; + +1; diff --git a/lib/VNWeb/ULists/List.pm b/lib/VNWeb/ULists/List.pm index a451e73c..4f2dcff2 100644 --- a/lib/VNWeb/ULists/List.pm +++ b/lib/VNWeb/ULists/List.pm @@ -77,6 +77,7 @@ sub filters_ { input_ type => 'submit', class => 'submit', tabindex => 10, value => 'Update filters'; input_ type => 'button', class => 'submit', tabindex => 10, id => 'managelabels', value => 'Manage labels' if $own; input_ type => 'button', class => 'submit', tabindex => 10, id => 'savedefault', value => 'Save as default' if $own; + input_ type => 'button', class => 'submit', tabindex => 10, id => 'exportlist', value => 'Export' if $own; }; }; } @@ -324,6 +325,15 @@ TUWF::get qr{/$RE{uid}/ulist}, sub { filters_ $own, $filtlabels, $opt, $opt_labels, \&url; elm_ 'UList.ManageLabels' if $own; elm_ 'UList.SaveDefault', $VNWeb::ULists::Elm::SAVED_OPTS_OUT, { uid => $u->{id}, opts => $opt } if $own; + div_ class => 'hidden exportlist', sub { + b_ 'Export your list'; + br_; + txt_ 'This function will export all visual novels and releases in your list, even those marked as private '; + txt_ '(there is currently no import function, more export options may be added later).'; + br_; + br_; + a_ href => "/u$u->{id}/list-export/xml", "Download XML export."; + }; } }; listing_ $u->{id}, $own, $opt, $labels, \&url if !$empty; |