summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2011-03-14 13:12:54 +0100
committerYorhel <git@yorhel.nl>2011-03-14 13:12:54 +0100
commit17d4f695b114f970c3b3c4c17c4b8915a0356297 (patch)
tree5e12ab47403fcf7919ebbde2a961c92506331850
parent4582a5dce7c0793fe64806d4cba5ab9d386ea878 (diff)
chardb: Added Char<->VN linking edit interface
Still somewhat quircky, but it works.
-rw-r--r--data/global.pl1
-rw-r--r--data/script.js207
-rw-r--r--data/style.css12
-rw-r--r--lib/VNDB/DB/Releases.pm2
-rw-r--r--lib/VNDB/Handler/Chars.pm14
-rw-r--r--lib/VNDB/Handler/Releases.pm34
-rw-r--r--lib/VNDB/Handler/Traits.pm2
-rwxr-xr-xutil/jsgen.pl1
8 files changed, 267 insertions, 6 deletions
diff --git a/data/global.pl b/data/global.pl
index 3f1ba3b6..5fd2bf85 100644
--- a/data/global.pl
+++ b/data/global.pl
@@ -108,6 +108,7 @@ our %S = (%S,
vnlist_status => [ 0..4 ],
blood_types => [qw| unknown a b ab o |],
genders => [qw| unknown m f b |],
+ char_roles => [qw| main primary side appears |],
atom_feeds => { # num_entries, title, id
announcements => [ 10, 'VNDB Site Announcements', '/t/an' ],
changes => [ 25, 'VNDB Recent Changes', '/hist' ],
diff --git a/data/script.js b/data/script.js
index f7b6eb1b..1aa60efa 100644
--- a/data/script.js
+++ b/data/script.js
@@ -1,5 +1,6 @@
/* function/attribute prefixes:
* ctr -> Character <-> trait linking
+ * cvn -> Character <-> VN linking
* date -> Date selector
* dd -> dropdown
* ds -> dropdown search
@@ -1774,7 +1775,7 @@ function ctrLoad() {
var t = ht.responseXML.getElementsByTagName('item');
for(var i=0; i<t.length; i++)
ctrAdd(t[i], v[t[i].getAttribute('id')]);
- });
+ }, 1);
else
ctrEmpty();
@@ -1786,7 +1787,7 @@ function ctrLoad() {
tr.appendChild(tag('td',
tag('b', {'class':'grayedout'}, g), item.firstChild.nodeValue,
tag('b', {'class':'grayedout'}, item.getAttribute('meta')=='yes' ? mt('_js_ds_tag_meta') : '')));
- }, ctrFormAdd, function () {});
+ }, ctrFormAdd);
}
function ctrEmpty() {
@@ -1884,6 +1885,208 @@ if(byId('traits_tbl'))
+/* C H A R A C T E R < - > V N L I N K I N G (/c+/edit) */
+
+// TODO: translate!
+
+function cvnLoad() {
+ // load current links
+ var l = byId('vns').value.split(' ');
+ var v = {}; // vid -> { rid: [ role, spoil ], .. }
+ var q = []; // list of v=X parameters
+ for(var i=0; i<l.length; i++) {
+ if(!l[i])
+ continue;
+ var m = l[i].split(/-/); // vid, rid, spoil, role
+ if(!v[m[0]]) {
+ q.push('v='+m[0]);
+ v[m[0]] = {};
+ }
+ v[m[0]][m[1]] = [ m[3], m[2] ];
+ }
+ if(q.length > 0)
+ ajax('/xml/releases.xml?'+q.join(';'), function(hr) {
+ var vns = byName(hr.responseXML, 'vn');
+ for(var i=0; i<vns.length; i++) {
+ var vid = vns[i].getAttribute('id');
+ cvnVNAdd(vns[i]);
+ var rels = byName(vns[i], 'release');
+ for(var r=0; r<rels.length; r++) {
+ var rid = rels[r].getAttribute('id');
+ if(v[vid][rid])
+ cvnRelAdd(vid, rid, v[vid][rid][0], v[vid][rid][1]);
+ }
+ if(v[vid][0])
+ cvnRelAdd(vid, 0, v[vid][0][0], v[vid][0][1]);
+ }
+ }, 1);
+ else
+ cvnEmpty();
+
+ // dropdown search
+ dsInit(byId('vns_input'), '/xml/vn.xml?q=', function(item, tr) {
+ tr.appendChild(tag('td', { style: 'text-align: right; padding-right: 5px'}, 'v'+item.getAttribute('id')));
+ tr.appendChild(tag('td', shorten(item.firstChild.nodeValue, 40)));
+ }, cvnFormAdd);
+}
+
+function cvnEmpty() {
+ var x = byId('vns_loading');
+ var t = byId('vns_tbl');
+ if(x)
+ t.removeChild(x);
+ var l = byName(t, 'tr');
+ var e = byId('vns_empty');
+ if(e && l.length > 1)
+ t.removeChild(e);
+ else if(!e && l.length < 1)
+ t.appendChild(tag('tr', {id:'vns_empty',colspan:3}, tag('td', 'No visual novels selected.')));
+}
+
+function cvnVNAdd(vn, rel) {
+ var vid = vn.getAttribute('id');
+ var rels = byName(vn, 'release');
+ byId('vns_tbl').appendChild(tag('tr', {id:'cvn_v'+vid, cvn_vid:vid, cvn_rels:rels},
+ tag('td', {'class':'tc_vn',colspan:4}, 'v'+vid+':',
+ tag('a', {href:'/v'+vid}, vn.getAttribute('title')),
+ tag('i', '(', tag('a', {href:'#', onclick:cvnRelNew}, 'add release'), ')')
+ )
+ ));
+ if(rel)
+ cvnRelAdd(vid, 0, 'primary', 0);
+ cvnEmpty();
+}
+
+function cvnRelAdd(vid, rid, role, spoil) {
+ var rels = byId('cvn_v'+vid).cvn_rels;
+ var rsel = tag('select', {onchange:cvnRelChange}, tag('option', {value:0}, 'All / others'));
+ for(var i=0; i<rels.length; i++) {
+ var id = rels[i].getAttribute('id');
+ rsel.appendChild(tag('option', {value: id, selected:id==rid},
+ '['+rels[i].getAttribute('lang')+'] '+rels[i].firstChild.nodeValue+' (r'+id+')'));
+ }
+
+ var lsel = tag('select', {onchange:cvnSerialize});
+ for(var i=0; i<char_roles.length; i++)
+ lsel.appendChild(tag('option', {value: char_roles[i], selected:char_roles[i]==role}, char_roles[i]));
+
+ // l10n /_spoil_\d+/
+ var ssel = tag('select', {onchange:cvnSerialize});
+ for(var i=0; i<3; i++)
+ ssel.appendChild(tag('option', {value:i, selected:i==spoil}, mt('_spoil_'+i)));
+
+ var tbl = byId('vns_tbl');
+ var l = byName(tbl, 'tr');
+ var last = null;
+ for(var i=1; i<l.length; i++)
+ if(l[i-1].cvn_vid == vid && l[i].cvn_vid != vid)
+ last = l[i-1];
+ tbl.insertBefore(tag('tr', {id:'cvn_v'+vid+'r'+rid, cvn_vid:vid, cvn_rid:rid},
+ tag('td', {'class':'tc_rel'}, rsel),
+ tag('td', {'class':'tc_rol'}, lsel),
+ tag('td', {'class':'tc_spl'}, ssel),
+ tag('td', {'class':'tc_del'}, tag('a', {href:'#', onclick:cvnRelDel}, 'remove'))
+ ), last);
+}
+
+function cvnRelChange() {
+ // look for duplicates and disallow the change
+ var val = this.options[this.selectedIndex].value;
+ var tr = this;
+ while(tr.nodeName.toLowerCase() != 'tr')
+ tr = tr.parentNode;
+ if(byId('cvn_v'+tr.cvn_vid+'r'+val)) {
+ alert('Release already selected.');
+ for(var i=0; i<this.options.length; i++)
+ this.options[i].selected = this.options[i].value == tr.cvn_rid;
+ return;
+ }
+ // otherwise, 'rename' this entry
+ tr.id = 'cvn_v'+tr.cvn_vid+'r'+val;
+ tr.cvn_rid = val;
+ cvnSerialize();
+}
+
+function cvnRelNew() {
+ var tr = this;
+ while(tr.nodeName.toLowerCase() != 'tr')
+ tr = tr.parentNode;
+ var id = 0;
+ if(byId('cvn_v'+tr.cvn_vid+'r0')) {
+ for(var i=0; i<tr.cvn_rels.length; i++) {
+ id = tr.cvn_rels[i].getAttribute('id');
+ if(!byId('cvn_v'+tr.cvn_vid+'r'+id))
+ break;
+ }
+ if(i == tr.cvn_rels.length) {
+ alert('All releases already selected');
+ return false;
+ }
+ }
+ cvnRelAdd(tr.cvn_vid, id, 'primary', 0);
+ cvnSerialize();
+ return false;
+}
+
+function cvnRelDel() {
+ var tbl = byId('vns_tbl');
+ var tr = this;
+ while(tr.nodeName.toLowerCase() != 'tr')
+ tr = tr.parentNode;
+ tbl.removeChild(tr);
+ var l = byName(tbl, 'tr');
+ var c = 0;
+ for(var i=0; i<l.length; i++)
+ if(l[i].cvn_vid == tr.cvn_vid)
+ c++;
+ if(c <= 1)
+ tbl.removeChild(byId('cvn_v'+tr.cvn_vid));
+ cvnSerialize();
+ cvnEmpty();
+ return false;
+}
+
+function cvnFormAdd(item) {
+ var inpt = byId('vns_input');
+ inpt.disabled = true;
+
+ ajax('/xml/releases.xml?v='+item.getAttribute('id'), function(hr) {
+ inpt.disabled = false;
+ inpt.value = '';
+
+ var items = byName(hr.responseXML, 'vn');
+ if(items.length < 1) // shouldn't happen
+ return alert('Oops! Error!');
+
+ var id = items[0].getAttribute('id');
+ if(byId('cvn_v'+id))
+ return alert(mt('VN already present.'));
+ cvnVNAdd(items[0], 1);
+ cvnSerialize();
+ }, 1);
+ return mt('_js_loading');
+}
+
+function cvnSerialize() {
+ var l = byName(byId('vns_tbl'), 'tr');
+ var v = [];
+ for(var i=0; i<l.length; i++)
+ if(l[i].cvn_rid != null) {
+ var rol = byName(byClass(l[i], 'tc_rol')[0], 'select')[0];
+ var spl = byName(byClass(l[i], 'tc_spl')[0], 'select')[0];
+ v.push(l[i].cvn_vid+'-'+l[i].cvn_rid+'-'+
+ spl.options[spl.selectedIndex].value+'-'+
+ rol.options[rol.selectedIndex].value);
+ }
+ byId('vns').value = v.join(' ');
+}
+
+if(byId('jt_box_chare_vns'))
+ cvnLoad();
+
+
+
+
/* F I L T E R S Y S T E M */
diff --git a/data/style.css b/data/style.css
index 27d0aeec..de55a177 100644
--- a/data/style.css
+++ b/data/style.css
@@ -872,6 +872,18 @@ div.chardetails table td.key { width: 80px; }
#jt_box_chare_traits td.tc_name { width: 200px }
#jt_box_chare_traits td.tc_name input { width: 280px; }
#jt_box_chare_traits td.tc_spoil { width: 80px; }
+#jt_box_chare_vns table { margin-bottom: 10px; margin-left: 10px; }
+#jt_box_chare_vns h2 { margin: 0 0 3px 0px; }
+#jt_box_chare_vns td.tc_vn { font-weight: bold; padding: 5px 0 3px 0 }
+#jt_box_chare_vns td.tc_vn i{ font-weight: normal; padding-left: 5px; font-style: normal }
+#jt_box_chare_vns td.tc_rel { width: 340px; padding-left: 15px }
+#jt_box_chare_vns td.tc_rel select { width: 340px; }
+#jt_box_chare_vns td.tc_rol,
+#jt_box_chare_vns td.tc_spl,
+#jt_box_chare_vns td.tc_rol select,
+#jt_box_chare_vns td.tc_spl select { width: 100px }
+#jt_box_chare_vns td.tc_del { padding-left: 5px }
+#jt_box_chare_vns td.tc_vnadd input { width: 280px }
/***** Documentation pages *****/
diff --git a/lib/VNDB/DB/Releases.pm b/lib/VNDB/DB/Releases.pm
index 87685ce6..bd2d5bd3 100644
--- a/lib/VNDB/DB/Releases.pm
+++ b/lib/VNDB/DB/Releases.pm
@@ -24,7 +24,7 @@ sub dbReleaseGet {
!$o{id} && !$o{rev} ? ( 'r.hidden = FALSE' => 0 ) : (),
$o{id} ? ( 'r.id = ?' => $o{id} ) : (),
$o{rev} ? ( 'c.rev = ?' => $o{rev} ) : (),
- $o{vid} ? ( 'rv.vid = ?' => $o{vid} ) : (),
+ $o{vid} ? ( 'rv.vid IN(!l)' => [ ref $o{vid} ? $o{vid} : [$o{vid}] ] ) : (),
$o{pid} ? ( 'rp.pid = ?' => $o{pid} ) : (),
defined $o{patch} ? ( 'rr.patch = ?' => $o{patch} == 1 ? 1 : 0) : (),
defined $o{freeware} ? ( 'rr.freeware = ?' => $o{freeware} == 1 ? 1 : 0) : (),
diff --git a/lib/VNDB/Handler/Chars.pm b/lib/VNDB/Handler/Chars.pm
index 64e7df2d..fa937915 100644
--- a/lib/VNDB/Handler/Chars.pm
+++ b/lib/VNDB/Handler/Chars.pm
@@ -141,6 +141,7 @@ sub page {
# vns
# TODO: handle spoilers!
+ # TODO: translate!
if(@{$r->{vns}}) {
my %vns;
push @{$vns{$_->{vid}}}, $_ for(sort { !defined($a->{rid})?1:!defined($b->{rid})?-1:$a->{rtitle} cmp $b->{rtitle} } @{$r->{vns}});
@@ -334,7 +335,18 @@ sub edit {
],
chare_vns => [ mt('_chare_vns'),
- [ input => short => 'vns', name => 'VNs (debug)' ],
+ [ hidden => short => 'vns' ],
+ [ static => nolabel => 1, content => sub {
+ h2 'Selected visual novels';
+ table; tbody id => 'vns_tbl';
+ Tr id => 'vns_loading'; td colspan => '4', mt('_js_loading'); end;
+ end; end;
+ h2 'Add visual novel';
+ table; Tr;
+ td class => 'tc_vnadd'; input id => 'vns_input', type => 'text', class => 'text'; end;
+ td colspan => 3, '';
+ end; end;
+ }],
]);
$self->htmlFooter;
}
diff --git a/lib/VNDB/Handler/Releases.pm b/lib/VNDB/Handler/Releases.pm
index a3ecbd42..159b7c3f 100644
--- a/lib/VNDB/Handler/Releases.pm
+++ b/lib/VNDB/Handler/Releases.pm
@@ -3,7 +3,7 @@ package VNDB::Handler::Releases;
use strict;
use warnings;
-use TUWF ':html', 'uri_escape';
+use TUWF ':html', ':xml', 'uri_escape';
use VNDB::Func;
@@ -13,6 +13,7 @@ TUWF::register(
qr{r} => \&browse,
qr{r(?:([1-9]\d*)(?:\.([1-9]\d*))?/(edit|copy))}
=> \&edit,
+ qr{xml/releases.xml} => \&relxml,
);
@@ -590,5 +591,36 @@ sub _fil_compat {
}
+sub relxml {
+ my $self = shift;
+
+ my $f = $self->formValidate(
+ { get => 'v', required => 1, multi => 1, mincount => 1, template => 'int' }
+ );
+ return $self->resNotFound if $f->{_err};
+
+ my $list = $self->dbReleaseGet(vid => $f->{v}, results => 100, what => 'vn');
+ my %vns = map +($_,0), @{$f->{v}};
+ for my $r (@$list) {
+ for my $v (@{$r->{vn}}) {
+ next if !exists $vns{$v->{vid}};
+ $vns{$v->{vid}} = [ $v ] if !$vns{$v->{vid}};
+ push @{$vns{$v->{vid}}}, $r;
+ }
+ }
+ $self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
+ xml;
+ tag 'vns';
+ for (sort { $a->[0]{title} cmp $b->[0]{title} } values %vns) {
+ my $v = shift @$_;
+ tag 'vn', id => $v->{vid}, title => $v->{title};
+ tag 'release', id => $_->{id}, lang => join(',', @{$_->{languages}}), $_->{title}
+ for (@$_);
+ end;
+ }
+ end;
+}
+
+
1;
diff --git a/lib/VNDB/Handler/Traits.pm b/lib/VNDB/Handler/Traits.pm
index a66721a8..887890b6 100644
--- a/lib/VNDB/Handler/Traits.pm
+++ b/lib/VNDB/Handler/Traits.pm
@@ -423,7 +423,7 @@ sub traitxml {
$self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
xml;
- tag 'tags', more => $np ? 'yes' : 'no';
+ tag 'traits', more => $np ? 'yes' : 'no';
for(@$list) {
tag 'item', id => $_->{id}, meta => $_->{meta} ? 'yes' : 'no', group => $_->{group}||'', groupname => $_->{groupname}||'', state => $_->{state}, $_->{name};
}
diff --git a/util/jsgen.pl b/util/jsgen.pl
index 112bfb7e..f046fe1a 100755
--- a/util/jsgen.pl
+++ b/util/jsgen.pl
@@ -105,6 +105,7 @@ sub jsgen {
$common .= sprintf "age_ratings = [ %s ];\n", join ',', @{$S{age_ratings}};
$common .= sprintf "languages = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{languages}};
$common .= sprintf "platforms = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{platforms}};
+ $common .= sprintf "char_roles = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{char_roles}};
$common .= sprintf "media = [ %s ];\n", join ', ', map qq{"$_"}, sort keys %{$S{media}};
$common .= sprintf "release_types = [ %s ];\n", join ', ', map qq{"$_"}, @{$S{release_types}};
$common .= sprintf "animated = [ %s ];\n", join ', ', @{$S{animated}};