summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2015-10-03 08:58:34 +0200
committerYorhel <git@yorhel.nl>2015-10-03 08:58:34 +0200
commit30caa944332fbbb9f6f5a4805f5beeee2b8a506c (patch)
tree9d3ffa6f316cb5f3723bf764172d8f49098e4d96
parent4cebc475aa4cd045d16e91616f148454d06d3c7d (diff)
Rewrote screenshot uploader to support multiple files + use json
This might have broken the screenshot uploader on some crappy browsers, but it's much cleaner than the old iframe hack. The ability to upload multiple files in one go is also very convenient.
-rw-r--r--data/js/lib.js12
-rw-r--r--data/js/vnscr.js282
-rw-r--r--data/lang.txt12
-rw-r--r--lib/VNDB/DB/VN.pm2
-rw-r--r--lib/VNDB/Handler/VNEdit.pm50
-rw-r--r--static/f/blank.css0
6 files changed, 169 insertions, 189 deletions
diff --git a/data/js/lib.js b/data/js/lib.js
index b01a93c8..675f2baa 100644
--- a/data/js/lib.js
+++ b/data/js/lib.js
@@ -3,10 +3,10 @@ window.collapsed_icon = '▸';
var ajax_req;
-window.ajax = function(url, func, async) {
+window.ajax = function(url, func, async, body) {
if(!async && ajax_req)
ajax_req.abort();
- var req = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
+ var req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
if(!async)
ajax_req = req;
req.onreadystatechange = function() {
@@ -16,9 +16,11 @@ window.ajax = function(url, func, async) {
return alert('Whoops, error! :(');
func(req);
};
- url += (url.indexOf('?')>=0 ? ';' : '?')+(Math.floor(Math.random()*999)+1);
- req.open('GET', url, true);
- req.send(null);
+ if(!body)
+ url += (url.indexOf('?')>=0 ? ';' : '?')+(Math.floor(Math.random()*999)+1);
+ req.open(body ? 'POST' : 'GET', url, true);
+ req.send(body);
+ return req;
};
diff --git a/data/js/vnscr.js b/data/js/vnscr.js
index c62eb982..21b1ba74 100644
--- a/data/js/vnscr.js
+++ b/data/js/vnscr.js
@@ -1,134 +1,135 @@
-var scrRel = [ [ 0, mt('_vnedit_scr_selrel') ] ];
-var scrStaticURL;
-var scrUplNr = 0;
-var scrDefRel;
-
-function scrLoad() {
- // get scrRel and scrStaticURL
- var rel = byId('scr_rel');
- scrStaticURL = rel.className;
- for(var i=0; i<rel.options.length; i++)
- scrRel[scrRel.length] = [ rel.options[i].value, getText(rel.options[i]) ];
- rel.parentNode.removeChild(rel);
- if(scrRel.length <= 2)
- scrRel.shift();
- scrDefRel = scrRel[0][0];
-
- // load the current screenshots
- var scr = byId('screenshots').value.split(' ');
- var siz = byId('screensizes').value.split(' ');
- for(i=0; i<scr.length && scr[i].length>1; i++) {
- var r = scr[i].split(',');
- var s = siz[i].split(',');
- scrSet(scrAdd(r[0], r[1], r[2]), s[0], s[1]);
+var rels;
+var defRid = 0;
+var staticUrl;
+
+function init() {
+ var data = jsonParse(getText(byId('screendata'))) || {};
+ rels = data.rel;
+ rels.unshift([ 0, mt('_vnedit_scr_selrel') ]);
+ staticUrl = data.staticurl;
+
+ var scr = jsonParse(byId('screenshots').value) || {};
+ for(i=0; i<scr.length; i++) {
+ var r = scr[i];
+ var s = data.size[r.id];
+ loaded(add(r.nsfw, r.rid), r.id, s[0], s[1]);
}
- ivInit();
- scrLast();
- scrSetSubmit();
-}
-
-function scrSetSubmit() {
var frm = byId('screenshots');
while(frm.nodeName.toLowerCase() != 'form')
frm = frm.parentNode;
- onSubmit(frm, function() {
- var loading = 0;
- var norelease = 0;
- var l = byName(byId('scr_table'), 'tr');
- for(var i=0; i<l.length-1; i++) {
- var rel = byName(l[i], 'select')[0];
- if(l[i].scr_status > 0)
- loading = 1;
- else if(rel.options[rel.selectedIndex].value == 0)
- norelease = 1;
- }
- if(loading) {
- alert(mt('_vnedit_scr_frmloading'));
- return false;
- } else if(norelease) {
- alert(mt('_vnedit_scr_frmnorel'));
- return false;
- }
- return true;
- });
+ onSubmit(frm, handleSubmit);
+
+ addLast();
+ ivInit();
}
-function scrURL(id, t) {
- return scrStaticURL+'/s'+t+'/'+(id%100<10?'0':'')+(id%100)+'/'+id+'.jpg';
+function handleSubmit() {
+ var loading = 0;
+ var norelease = 0;
+
+ var r = [];
+ var l = byName(byId('scr_table'), 'tr');
+ for(var i=0; i<l.length-1; i++)
+ if(l[i].scr_loading)
+ loading = 1;
+ else if(l[i].scr_rid == 0)
+ norelease = 1;
+ else
+ r.push({ rid: l[i].scr_rid, nsfw: l[i].scr_nsfw, id: l[i].scr_id });
+
+ if(loading)
+ alert(mt('_vnedit_scr_frmloading'));
+ else if(norelease)
+ alert(mt('_vnedit_scr_frmnorel'));
+ else
+ byId('screenshots').value = JSON.stringify(r);
+ return !loading && !norelease;
}
-function scrAdd(id, nsfw, rel) {
- // tr.scr_status = 0: done, 1: uploading
+function genRels(sel) {
+ var r = tag('select', {'class':'scr_relsel'});
+ for(var i=0; i<rels.length; i++)
+ r.appendChild(tag('option', {value: rels[i][0], selected: rels[i][0] == sel}, rels[i][1]));
+ return r;
+}
+
+function URL(id, t) {
+ return staticUrl+'/s'+t+'/'+(id%100<10?'0':'')+(id%100)+'/'+id+'.jpg';
+}
- var tr = tag('tr', { id:'scr_tr_'+id, scr_id: id, scr_status: 1, scr_rel: rel, scr_nsfw: nsfw},
+// Need to run addLast() after this function
+function add(nsfw, rid) {
+ var tr = tag('tr', { scr_id: 0, scr_loading: 1, scr_rid: rid, scr_nsfw: nsfw?1:0},
tag('td', { 'class': 'thumb'}, mt('_js_loading')),
tag('td',
tag('b', mt('_vnedit_scr_uploading')),
tag('br', null),
- id ? null : mt('_vnedit_scr_upl_msg'),
+ mt('_vnedit_scr_upl_msg'),
tag('br', null),
- id ? null : tag('a', {href:'#', onclick:scrDel}, mt('_vnedit_scr_cancel'))
+ tag('a', {href:'#', onclick:del}, mt('_vnedit_scr_cancel'))
)
);
byId('scr_table').appendChild(tr);
return tr;
}
-function scrSet(tr, width, height) {
- var dim = width+'x'+height;
- tr.scr_status = 0;
-
- // image
- setContent(byName(tr, 'td')[0],
- tag('a', {href: scrURL(tr.scr_id, 'f'), rel:'iv:'+dim+':edit'},
- tag('img', {src: scrURL(tr.scr_id, 't')})
- )
- );
-
- // check full resolution with the list of DB-defined resolutions
- var odd = true;
+function oddDim(dim) {
if(dim == '256x384') // special-case NDS resolution (not in the DB)
- odd = false;
- for(var j=0; j<VARS.resolutions.length && odd; j++) {
+ return false;
+ for(var j=0; j<VARS.resolutions.length; j++) {
if(typeof VARS.resolutions[j][1] != 'object') {
if(VARS.resolutions[j][0] == dim)
- odd = false;
+ return false;
} else {
for(var k=1; k<VARS.resolutions[j].length; k++)
if(VARS.resolutions[j][k][1] == dim)
- odd = false;
+ return false;;
}
}
+ return true;
+}
- // content
- var rel = tag('select', {onchange: scrSerialize, 'class':'scr_relsel'});
- for(var j=0; j<scrRel.length; j++)
- rel.appendChild(tag('option', {value: scrRel[j][0], selected: tr.scr_rel == scrRel[j][0]}, scrRel[j][1]));
- var nsfwid = 'scr_sfw_'+tr.scr_id;
+// Need to run ivInit() after this function
+function loaded(tr, id, width, height) {
+ var dim = width+'x'+height;
+ tr.id = 'scr_tr_'+id;
+ tr.scr_id = id;
+ tr.scr_loading = 0;
+
+ setContent(byName(tr, 'td')[0],
+ tag('a', {href: URL(tr.scr_id, 'f'), rel:'iv:'+dim+':edit'},
+ tag('img', {src: URL(tr.scr_id, 't')})
+ )
+ );
+
+ var rel = genRels(tr.scr_rid);
+ rel.onchange = function() { tr.scr_rid = this.options[this.selectedIndex].value };
+
+ var nsfwid = 'scr_nsfw_'+id;
setContent(byName(tr, 'td')[1],
- tag('b', mt('_vnedit_scr_id', tr.scr_id)),
- ' (', tag('a', {href: '#', onclick:scrDel}, mt('_js_remove')), ')',
+ tag('b', mt('_vnedit_scr_id', id)),
+ ' (', tag('a', {href: '#', onclick:del}, mt('_js_remove')), ')',
tag('br', null),
mt('_vnedit_scr_fullsize', dim),
- odd ? tag('b', {'class':'standout', 'style':'font-weight: bold'}, ' '+mt('_vnedit_scr_nonstandard')) : null,
+ oddDim(dim) ? tag('b', {'class':'standout', 'style':'font-weight: bold'}, ' '+mt('_vnedit_scr_nonstandard')) : null,
tag('br', null),
tag('br', null),
- tag('input', {type:'checkbox', onclick:scrSerialize, id:nsfwid, name:nsfwid, checked: tr.scr_nsfw>0, 'class':'scr_nsfw'}),
+ tag('input', {type:'checkbox', name:nsfwid, id:nsfwid, checked: tr.scr_nsfw!=0, onclick: function() { tr.scr_nsfw = this.checked?1:0 }, 'class':'scr_nsfw'}),
tag('label', {'for':nsfwid}, mt('_vnedit_scr_nsfw')),
tag('br', null),
rel
);
}
-function scrLast() {
+function addLast() {
if(byId('scr_last'))
byId('scr_table').removeChild(byId('scr_last'));
var full = byName(byId('scr_table'), 'tr').length >= 10;
- var rel = tag('select', {onchange: function(){scrDefRel=this.options[this.selectedIndex].value}, 'class':'scr_relsel', 'id':'scradd_relsel'});
- for(var j=0; j<scrRel.length; j++)
- rel.appendChild(tag('option', {value: scrRel[j][0], selected: scrDefRel == scrRel[j][0]}, scrRel[j][1]));
+ var rel = genRels(defRid);
+ rel.onchange = function() { defRid = this.options[this.selectedIndex].value };
+ rel.id = 'scradd_relsel';
byId('scr_table').appendChild(tag('tr', {id:'scr_last'},
tag('td', {'class': 'thumb'}),
@@ -143,91 +144,60 @@ function scrLast() {
tag('br', null),
rel,
tag('br', null),
- tag('input', {name:'scr_upload', id:'scr_upload', type:'file', 'class':'text'}),
+ tag('input', {name:'scr_upload', id:'scr_upload', type:'file', 'class':'text', multiple:true}),
tag('br', null),
- tag('input', {type:'button', value:mt('_vnedit_scr_addbut'), 'class':'submit', onclick:scrUpload})
+ tag('input', {type:'button', value:mt('_vnedit_scr_addbut'), 'class':'submit', onclick:upload})
)
));
}
-function scrDel(what) {
- var tr = what && what.scr_status != null ? what : this;
- while(tr.nodeName.toLowerCase() != 'tr')
+function del(what) {
+ var tr = what && what.scr_id != null ? what : this;
+ while(tr.scr_id == null)
tr = tr.parentNode;
- tr.scr_status = null;
- if(tr.scr_upl && byId(tr.scr_upl))
- byId(tr.scr_upl).parentNode.removeChild(byId(tr.scr_upl));
+ if(tr.scr_ajax)
+ tr.scr_ajax.abort();
byId('scr_table').removeChild(tr);
- scrSerialize();
- scrLast();
+ addLast();
ivInit();
return false;
}
-function scrUpload() {
- scrUplNr++;
-
- // create temporary form
- var ifid = 'scr_upl_'+scrUplNr;
- var frm = tag('form', {method: 'post', action:'/xml/screenshots.xml?upload='+scrUplNr,
- target: ifid, enctype:'multipart/form-data'});
- var ifr = tag('iframe', {id:ifid, name:ifid, src:'about:blank', onload:scrUploadComplete});
- addBody(tag('div', {'class':'scr_uploader'}, ifr, frm));
-
- // submit form
- var upl = byId('scr_upload');
- upl.id = upl.name = 'scr_upl_file_'+scrUplNr;
- frm.appendChild(upl);
- frm.submit();
- ifr.scr_tr = scrAdd(0, 0, 0);
- ifr.scr_upl = ifid;
- ifr.scr_tr.scr_rel = byId('scradd_relsel').options[byId('scradd_relsel').selectedIndex].value;
- scrLast();
- return false;
-}
-
-function scrUploadComplete() {
- var ifr = this;
- var fr = window.frames[ifr.id];
- if(fr.location.href.indexOf('screenshots') < 0)
- return;
-
- var tr = ifr.scr_tr;
- if(tr && tr.scr_status == 1) {
- try {
- tr.scr_id = fr.window.document.getElementsByTagName('image')[0].getAttribute('id');
- } catch(e) {
- tr.scr_id = -10;
- }
- if(tr.scr_id < 0) {
- alert(tr.scr_id == -10 ? mt('_vnedit_scr_oops') :
- tr.scr_id == -1 ? mt('_vnedit_scr_errformat') : mt('_vnedit_scr_errempty'));
- scrDel(tr);
+function uploadFile(f) {
+ var tr = add(0, defRid);
+ var fname = f.name;
+ var frm = new FormData();
+ frm.append('file', f);
+ tr.scr_ajax = ajax('/xml/screenshots.xml', function(hr) {
+ tr.scr_ajax = null;
+ var img = hr.responseXML.getElementsByTagName('image')[0];
+ var id = img.getAttribute('id');
+ if(id < 0) {
+ alert(fname + ":\n" + (id == -1 ? mt('_vnedit_scr_errformat') : mt('_vnedit_scr_errempty')));
+ del(tr);
} else {
- tr.id = 'scr_tr_'+tr.scr_id;
- scrSet(tr, fr.window.document.getElementsByTagName('image')[0].getAttribute('width'), fr.window.document.getElementsByTagName('image')[0].getAttribute('height'));
- scrSerialize();
+ loaded(tr, id, img.getAttribute('width'), img.getAttribute('height'));
ivInit();
}
- }
-
- tr.scr_upl = null;
- /* remove the <div> in a timeout, otherwise some browsers think the page is still loading */
- setTimeout(function() { ifr.parentNode.parentNode.removeChild(ifr.parentNode) }, 1000);
+ }, true, frm);
}
-function scrSerialize() {
- var r = [];
- var l = byName(byId('scr_table'), 'tr');
- for(var i=0; i<l.length-1; i++)
- if(l[i].scr_status == 0)
- r[r.length] = [
- l[i].scr_id,
- byClass(l[i], 'input', 'scr_nsfw')[0].checked ? 1 : 0,
- scrRel[byClass(l[i], 'select', 'scr_relsel')[0].selectedIndex][0]
- ].join(',');
- byId('screenshots').value = r.join(' ');
+function upload() {
+ var files = byId('scr_upload').files;
+
+ if(files.length < 1) {
+ alert(mt('_vnedit_scr_errempty'));
+ return false;
+ } else if(files.length + byName(byId('scr_table'), 'tr').length - 1 > 10) {
+ alert(mt('_vnedit_scr_errtoomany'));
+ return false;
+ }
+
+ for(var i=0; i<files.length; i++)
+ uploadFile(files[i]);
+ addLast();
+ return false;
}
-if(byId('jt_box_vn_scr') && byId('scr_table'))
- scrLoad();
+if(byId('jt_box_vn_scr'))
+ init();
diff --git a/data/lang.txt b/data/lang.txt
index 1fcc2103..ccfee14c 100644
--- a/data/lang.txt
+++ b/data/lang.txt
@@ -14501,6 +14501,18 @@ uk : Помилка завантаження!
it : Caricamento fallito!
Nessun file selezionato, o un file vuoto?
+:_vnedit_scr_errtoomany
+en : Too many files selected. The total number of screenshots may not exceed 10.
+ru*:
+cs*:
+hu*:
+nl*:
+de*:
+es*:
+tr*:
+uk*:
+it*:
+
# VN Relation graph page (/v+/rg)
diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm
index d3b37315..54c410b5 100644
--- a/lib/VNDB/DB/VN.pm
+++ b/lib/VNDB/DB/VN.pm
@@ -239,7 +239,7 @@ sub dbVNRevisionInsert {
if($o->{screenshots}) {
$self->dbExec('DELETE FROM edit_vn_screenshots');
my $q = join ',', map '(?, ?, ?)', @{$o->{screenshots}};
- my @val = map +($_->[0], $_->[1]?1:0, $_->[2]), @{$o->{screenshots}};
+ my @val = map +($_->{id}, $_->{nsfw}?1:0, $_->{rid}), @{$o->{screenshots}};
$self->dbExec("INSERT INTO edit_vn_screenshots (scr, nsfw, rid) VALUES $q", @val) if @val;
}
diff --git a/lib/VNDB/Handler/VNEdit.pm b/lib/VNDB/Handler/VNEdit.pm
index b1e3a548..01a33241 100644
--- a/lib/VNDB/Handler/VNEdit.pm
+++ b/lib/VNDB/Handler/VNEdit.pm
@@ -99,7 +99,10 @@ sub edit {
],
anime => join(' ', sort { $a <=> $b } map $_->{id}, @{$v->{anime}}),
vnrelations => join('|||', map $_->{relation}.','.$_->{id}.','.($_->{official}?1:0).','.$_->{title}, sort { $a->{id} <=> $b->{id} } @{$v->{relations}}),
- screenshots => join(' ', map sprintf('%d,%d,%d', $_->{id}, $_->{nsfw}?1:0, $_->{rid}), @{$v->{screenshots}}),
+ screenshots => json_encode [
+ map +{ id => $_->{id}, nsfw => $_->{nsfw}?1:0, rid => $_->{rid} },
+ sort { $a->{id} <=> $b->{id} } @{$v->{screenshots}}
+ ]
);
my $frm;
@@ -128,7 +131,11 @@ sub edit {
{ field => 'note', required => 0, maxlength => 250, default => '' },
]},
{ post => 'vnrelations', required => 0, default => '', maxlength => 5000 },
- { post => 'screenshots', required => 0, default => '', maxlength => 1000 },
+ { post => 'screenshots', required => 0, template => 'json', json_fields => [
+ { field => 'id', required => 1, template => 'id' },
+ { field => 'rid', required => 1, template => 'id' },
+ { field => 'nsfw', required => 1, template => 'uint', enum => [0,1] },
+ ]},
{ post => 'editsum', required => !$nosubmit, template => 'editsum' },
{ post => 'ihid', required => 0 },
{ post => 'ilock', required => 0 },
@@ -178,7 +185,7 @@ sub edit {
# parse and re-sort fields that have multiple representations of the same information
my $anime = { map +($_=>1), grep /^[0-9]+$/, split /[ ,]+/, $frm->{anime} };
my $relations = [ map { /^([a-z]+),([0-9]+),([01]),(.+)$/ && (!$vid || $2 != $vid) ? [ $1, $2, $3, $4 ] : () } split /\|\|\|/, $frm->{vnrelations} ];
- my $screenshots = [ map /^[0-9]+,[01],[0-9]+$/ ? [split /,/] : (), split / +/, $frm->{screenshots} ];
+ my $screenshots = json_decode $frm->{screenshots};
$frm->{ihid} = $frm->{ihid}?1:0;
$frm->{ilock} = $frm->{ilock}?1:0;
@@ -187,7 +194,7 @@ sub edit {
$frm->{anime} = join ' ', sort { $a <=> $b } keys %$anime;
$frm->{vnrelations} = join '|||', map $_->[0].','.$_->[1].','.($_->[2]?1:0).','.$_->[3], sort { $a->[1] <=> $b->[1]} @{$relations};
$frm->{img_nsfw} = $frm->{img_nsfw} ? 1 : 0;
- $frm->{screenshots} = join ' ', map sprintf('%d,%d,%d', $_->[0], $_->[1]?1:0, $_->[2]), sort { $a->[0] <=> $b->[0] } @$screenshots;
+ $frm->{screenshots} = json_encode [ sort { $a->{id} <=> $b->{id} } @$screenshots ];
$frm->{credits} = json_encode \@credits;
$frm->{seiyuu} = json_encode \@seiyuu;
@@ -420,13 +427,15 @@ sub _form {
[ static => nolabel => 1, content => mt '_vnedit_scrnorel' ],
) : (
[ hidden => short => 'screenshots' ],
- [ hidden => short => 'screensizes', value => do {
- # Current screenshot resolutions, for use by Javascript
- my @scr = map /^(\d+),/?$1:(), split / /, $frm->{screenshots};
- my %scr = map +($_->{id}, "$_->{width},$_->{height}"), @scr ? @{$self->dbScreenshotGet(\@scr)} : ();
- join ' ', map $scr{$_}, @scr;
- }],
[ static => nolabel => 1, content => sub {
+ my @scr = map $_->{id}, @{ json_decode $frm->{screenshots} };
+ my %scr = map +($_->{id}, [ $_->{width}, $_->{height}]), @scr ? @{$self->dbScreenshotGet(\@scr)} : ();
+ my @rels = map [ $_->{id}, sprintf '[%s] %s (r%d)', join(',', @{$_->{languages}}), $_->{title}, $_->{id} ], @$r;
+ script_json screendata => {
+ size => \%scr,
+ rel => \@rels,
+ staticurl => $self->{url_static},
+ };
div class => 'warning';
lit mt '_vnedit_scrmsg';
end;
@@ -434,9 +443,6 @@ sub _form {
table class => 'stripe';
tbody id => 'scr_table', '';
end;
- Select id => 'scr_rel', class => $self->{url_static};
- option value => $_->{id}, sprintf '[%s] %s (r%d)', join(',', @{$_->{languages}}), $_->{title}, $_->{id} for (@$r);
- end;
}],
)]
@@ -504,24 +510,15 @@ sub vnxml {
# handles uploading screenshots and fetching information about them
sub scrxml {
my $self = shift;
- return $self->htmlDenied if !$self->authCan('edit');
- $self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
-
- # fetch information about screenshots
- die "This page can only be accessed as POST\n" if $self->reqMethod ne 'POST';
+ return $self->htmlDenied if !$self->authCan('edit') || $self->reqMethod ne 'POST';
# upload new screenshot
- my $num = $self->formValidate({get => 'upload', template => 'uint'});
- return $self->resNotFound if $num->{_err};
- my $param = "scr_upl_file_$num->{upload}";
-
- # check for simple errors
my $id = 0;
- my $imgdata = $self->reqUploadRaw($param);
+ my $imgdata = $self->reqUploadRaw('file');
$id = -2 if !$imgdata;
$id = -1 if !$id && $imgdata !~ /^(\xff\xd8|\x89\x50)/; # JPG or PNG headers
- # no error? save and let Multi process it
+ # no error? process it
my($ow, $oh);
if(!$id) {
my $im = Image::Magick->new;
@@ -546,9 +543,8 @@ sub scrxml {
chmod 0666, $fn;
}
+ $self->resHeader('Content-type' => 'text/xml; charset=UTF-8');
xml;
- # blank stylesheet because some browsers don't allow JS access otherwise
- lit qq|<?xml-stylesheet href="$self->{url_static}/f/blank.css" type="text/css" ?>|;
tag 'image', id => $id, $id > 0 ? (width => $ow, height => $oh) : (), undef;
}
diff --git a/static/f/blank.css b/static/f/blank.css
deleted file mode 100644
index e69de29b..00000000
--- a/static/f/blank.css
+++ /dev/null