summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2020-07-15 13:40:59 +0200
committerYorhel <git@yorhel.nl>2020-07-15 13:41:02 +0200
commit8d4d759256316de49710796152488ba1da09f731 (patch)
tree93346ca4c2916cf360cc2366040b266fbc011b64
parent1690494ea55ae3e1e4fa002d5fd8bd1999c431a5 (diff)
UList::Export: Add initial list export functionality
-rw-r--r--data/style.css1
-rw-r--r--elm/UList/ManageLabels.js8
-rw-r--r--elm/UList/SaveDefault.js7
-rw-r--r--elm/UList/actiontabs.js17
-rw-r--r--lib/VNWeb/ULists/Export.pm101
-rw-r--r--lib/VNWeb/ULists/List.pm10
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;