diff options
author | Yorhel <git@yorhel.nl> | 2015-10-03 08:58:34 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2015-10-03 08:58:34 +0200 |
commit | 30caa944332fbbb9f6f5a4805f5beeee2b8a506c (patch) | |
tree | 9d3ffa6f316cb5f3723bf764172d8f49098e4d96 | |
parent | 4cebc475aa4cd045d16e91616f148454d06d3c7d (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.js | 12 | ||||
-rw-r--r-- | data/js/vnscr.js | 282 | ||||
-rw-r--r-- | data/lang.txt | 12 | ||||
-rw-r--r-- | lib/VNDB/DB/VN.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNEdit.pm | 50 | ||||
-rw-r--r-- | static/f/blank.css | 0 |
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 |