Revision e116c100
Von Sven Schöling vor mehr als 3 Jahren hinzugefügt
SL/Controller/ImageUpload.pm | ||
---|---|---|
28 | 28 |
my ($self) = @_; |
29 | 29 |
|
30 | 30 |
$::request->layout->add_javascripts('kivi.File.js'); |
31 |
$::request->layout->add_javascripts('kivi.FileDB.js'); |
|
32 |
$::request->layout->add_javascripts('kivi.ImageUpload.js'); |
|
31 | 33 |
|
32 |
$self->render('image_upload/form');
|
|
34 |
$self->render('image_upload/local_list');
|
|
33 | 35 |
} |
34 | 36 |
|
35 | 37 |
################# internal ############### |
js/kivi.FileDB.js | ||
---|---|---|
1 |
namespace("kivi.FileDB", function(ns) { |
|
2 |
"use strict"; |
|
3 |
|
|
4 |
const database = 'kivi'; |
|
5 |
const store = 'files'; |
|
6 |
const db_version = 1; |
|
7 |
|
|
8 |
// IndexedDB |
|
9 |
const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB; |
|
10 |
|
|
11 |
// Create/open database |
|
12 |
let db; |
|
13 |
let request = indexedDB.open(database, db_version); |
|
14 |
request.onupgradeneeded = (event) => { |
|
15 |
ns.create_image_store(event.target.result); |
|
16 |
}; |
|
17 |
request.onerror = ns.onerror; |
|
18 |
request.onsuccess = () => { |
|
19 |
db = request.result; |
|
20 |
|
|
21 |
db.onerror = (event) => { |
|
22 |
console.error("Error creating/accessing IndexedDB database"); |
|
23 |
console.error(event); |
|
24 |
}; |
|
25 |
|
|
26 |
// Interim solution for Google Chrome to create an objectStore. Will be deprecated |
|
27 |
if (db.setVersion) { |
|
28 |
if (db.version != db_version) { |
|
29 |
let setVersion = db.setVersion(db_version); |
|
30 |
setVersion.onsuccess = () => { |
|
31 |
ns.create_image_store(db); |
|
32 |
}; |
|
33 |
} |
|
34 |
} |
|
35 |
}; |
|
36 |
|
|
37 |
ns.create_image_store = function (db) { |
|
38 |
db.createObjectStore(store, { autoIncrement : true }); |
|
39 |
}; |
|
40 |
|
|
41 |
ns.store_image = function (blob, filename, success) { |
|
42 |
let put_request = ns.open_store("readwrite").add(blob, filename); |
|
43 |
|
|
44 |
put_request.onsuccess = success; |
|
45 |
put_request.on_error = ns.onerror; |
|
46 |
}; |
|
47 |
|
|
48 |
ns.retrieve_image = function(key, success) { |
|
49 |
let get_request = ns.open_store().objectStore(store).get(key); |
|
50 |
|
|
51 |
get_request.onsuccess = success; |
|
52 |
get_request.onerror = request.onerror; |
|
53 |
}; |
|
54 |
|
|
55 |
ns.retrieve_all = function(success) { |
|
56 |
let request = ns.open_store().getAll(); |
|
57 |
request.onsuccess = (event) => { success(event.target.result); }; |
|
58 |
request.onerror = ns.error; |
|
59 |
}; |
|
60 |
|
|
61 |
ns.retrieve_all_keys = function(success) { |
|
62 |
let request = ns.open_store().getAllKeys(); |
|
63 |
request.onsuccess = (event) => { success(event.target.result); }; |
|
64 |
request.onerror = ns.error; |
|
65 |
}; |
|
66 |
|
|
67 |
ns.delete_all= function() { |
|
68 |
ns.retrieve_all_keys((keys) => { |
|
69 |
keys.forEach((key) => ns.delete_key(key)); |
|
70 |
}); |
|
71 |
}; |
|
72 |
|
|
73 |
ns.delete_key= function(key, success) { |
|
74 |
let request = ns.open_store("readwrite").delete(key); |
|
75 |
request.onsuccess = (event) => { if (success) success(event.target.result); }; |
|
76 |
request.onerror = ns.error; |
|
77 |
}; |
|
78 |
|
|
79 |
ns.open_store = function(mode = "readonly") { |
|
80 |
return db.transaction([store], mode).objectStore(store); |
|
81 |
}; |
|
82 |
|
|
83 |
ns.onerror = (event) => { |
|
84 |
console.error("Error creating/accessing IndexedDB database"); |
|
85 |
console.error(event.errorState); |
|
86 |
}; |
|
87 |
}); |
js/kivi.ImageUpload.js | ||
---|---|---|
1 |
namespace("kivi.ImageUpload", function(ns) { |
|
2 |
"use strict"; |
|
3 |
|
|
4 |
ns.add_files = function(target) { |
|
5 |
let files = []; |
|
6 |
for (var i = 0; i < target.files.length; i++) { |
|
7 |
files.push(target.files.item(i)); |
|
8 |
} |
|
9 |
|
|
10 |
kivi.FileDB.store_image(files[0], files[0].name, () => { |
|
11 |
ns.reload_images(); |
|
12 |
target.value = null; |
|
13 |
}); |
|
14 |
}; |
|
15 |
|
|
16 |
ns.reload_images = function() { |
|
17 |
kivi.FileDB.retrieve_all((data) => { |
|
18 |
$('#stored-images').empty(); |
|
19 |
data.forEach(ns.create_thumb_row); |
|
20 |
}); |
|
21 |
}; |
|
22 |
|
|
23 |
ns.create_thumb_row = function(file) { |
|
24 |
let URL = window.URL || window.webkitURL; |
|
25 |
let file_url = URL.createObjectURL(file); |
|
26 |
|
|
27 |
let $row = $("<div>").addClass("row image-upload-row"); |
|
28 |
let $button = $("<a>") |
|
29 |
.addClass("btn-floating btn-large waves-effect waves-light red") |
|
30 |
.click((event) => ns.remove_image(event, file.name)) |
|
31 |
.append($("<i>delete</i>").addClass("material-icons")); |
|
32 |
$row.append($("<div>").addClass("col s3").append($button)); |
|
33 |
|
|
34 |
let $image = $('<img>').attr("src", file_url).addClass("materialboxed responsive-img"); |
|
35 |
$row.append($("<div>").addClass("col s9").append($image)); |
|
36 |
|
|
37 |
$("#stored-images").append($row); |
|
38 |
}; |
|
39 |
|
|
40 |
ns.remove_image = function(event, key) { |
|
41 |
let $row = $(event.target).closest(".image-upload-row"); |
|
42 |
kivi.FileDB.delete_key(key, () => { |
|
43 |
$row.remove(); |
|
44 |
}); |
|
45 |
}; |
|
46 |
|
|
47 |
ns.upload_selected_files = function(id,type,filetype,maxsize) { |
|
48 |
kivi.FileDB.retrieve_all((myfiles) => { |
|
49 |
let filesize = 0; |
|
50 |
myfiles.forEach(file => { |
|
51 |
filesize += file.size; |
|
52 |
if (filesize > maxsize) { |
|
53 |
$("#upload_result").html(kivi.t8("filesize too big: ") + filesize+ kivi.t8(" bytes, max=") + maxsize ); |
|
54 |
return; |
|
55 |
} |
|
56 |
|
|
57 |
let data = new FormData(); |
|
58 |
data.append(file); |
|
59 |
data.append("action", "File/ajax_files_uploaded"); |
|
60 |
data.append("json", "1"); |
|
61 |
data.append("object_type", type); |
|
62 |
data.append("object_id", id); |
|
63 |
data.append("file_type", filetype); |
|
64 |
|
|
65 |
$("#upload_result").html(kivi.t8("start upload")); |
|
66 |
|
|
67 |
$.ajax({ |
|
68 |
url: "controller.pl", |
|
69 |
data: data, |
|
70 |
success: ns.attSuccess, |
|
71 |
progress: ns.attProgress, |
|
72 |
error: ns.attFailes, |
|
73 |
abort: ns.attCanceled |
|
74 |
}); |
|
75 |
}); |
|
76 |
}); |
|
77 |
}; |
|
78 |
|
|
79 |
ns.attProgress = function(event) { |
|
80 |
if (event.lengthComputable) { |
|
81 |
var percentComplete = (event.loaded / event.total) * 100; |
|
82 |
$("#upload_result").html(percentComplete+" % "+ kivi.t8("uploaded")); |
|
83 |
} |
|
84 |
}; |
|
85 |
|
|
86 |
ns.attFailed = function() { |
|
87 |
$('#upload_modal').modal('close'); |
|
88 |
$("#upload_result").html(kivi.t8("An error occurred while transferring the file.")); |
|
89 |
}; |
|
90 |
|
|
91 |
ns.attCanceled = function() { |
|
92 |
$('#upload_modal').modal('close'); |
|
93 |
$("#upload_result").html(kivi.t8("The transfer has been canceled by the user.")); |
|
94 |
}; |
|
95 |
|
|
96 |
ns.attSuccess = function() { |
|
97 |
$('#upload_modal').modal('close'); |
|
98 |
$("#upload_result").html(kivi.t8("Files have been uploaded successfully.")); |
|
99 |
}; |
|
100 |
|
|
101 |
ns.init = function() { |
|
102 |
ns.reload_images(); |
|
103 |
}; |
|
104 |
|
|
105 |
|
|
106 |
}); |
|
107 |
|
|
108 |
$(kivi.ImageUpload.init); |
js/kivi.Materialize.js | ||
---|---|---|
3 | 3 |
|
4 | 4 |
ns.init = function() { |
5 | 5 |
ns.reinit_widgets(); |
6 |
} |
|
6 |
};
|
|
7 | 7 |
|
8 |
ns.build_i18n = function(locale) {
|
|
8 |
ns.build_i18n = function() { |
|
9 | 9 |
return { |
10 | 10 |
months: [ |
11 | 11 |
kivi.t8('January'), |
... | ... | |
63 | 63 |
// Accessibility labels |
64 | 64 |
labelMonthNext: kivi.t8('Next month'), |
65 | 65 |
labelMonthPrev: kivi.t8('Previous month') |
66 |
} |
|
67 |
} |
|
66 |
};
|
|
67 |
};
|
|
68 | 68 |
|
69 | 69 |
ns.reinit_widgets = function() { |
70 | 70 |
$('.sidenav').sidenav(); |
... | ... | |
76 | 76 |
i18n: ns.build_i18n() |
77 | 77 |
}); |
78 | 78 |
$('.modal').modal(); |
79 |
$('.materialboxed').materialbox(); |
|
79 | 80 |
M.updateTextFields(); |
80 |
} |
|
81 |
};
|
|
81 | 82 |
|
82 | 83 |
// alternative for kivi.popup_dialog. |
83 | 84 |
// opens materialize modal instead. |
84 | 85 |
// |
85 | 86 |
// differences: M.modal can not load external content, so it needs to be fetched manually and inserted into the DOM. |
86 | 87 |
ns.popup_dialog = function(params) { |
87 |
console.log(params); |
|
88 | 88 |
params = params || { }; |
89 | 89 |
let id = params.id || 'jqueryui_popup_dialog'; |
90 | 90 |
let $div; |
... | ... | |
94 | 94 |
// unlike classic layout, there is not fixed size, and M.modal is always... modal |
95 | 95 |
onCloseStart: custom_close |
96 | 96 |
}, |
97 |
// User supplied options:
|
|
97 |
// User supplied options: |
|
98 | 98 |
params.dialog || { }, |
99 | 99 |
{ // Options that must not be changed: |
100 | 100 |
// close options already work |
... | ... | |
110 | 110 |
params.data = undefined; |
111 | 111 |
ns.popup_dialog(params); |
112 | 112 |
}, |
113 |
error: function(x, status, error) { console.log(error); },
|
|
113 |
error: function(x, status, error) { console.error(error); },
|
|
114 | 114 |
dataType: 'text', |
115 | 115 |
}); |
116 | 116 |
return 1; |
... | ... | |
118 | 118 |
|
119 | 119 |
if (params.html) { |
120 | 120 |
$div = $('<div>'); |
121 |
$div.attr('id', id) |
|
121 |
$div.attr('id', id);
|
|
122 | 122 |
$div.addClass("modal"); |
123 | 123 |
let $modal_content = $('<div>'); |
124 | 124 |
$modal_content.addClass('modal-content'); |
... | ... | |
126 | 126 |
$div.append($modal_content); |
127 | 127 |
$('body').append($div); |
128 | 128 |
kivi.reinit_widgets(); |
129 |
dialog_params.onCloseEnd = function() { $div.remove(); } |
|
129 |
dialog_params.onCloseEnd = function() { $div.remove(); };
|
|
130 | 130 |
|
131 | 131 |
$div.modal(dialog_params); |
132 |
|
|
133 | 132 |
} else if(params.id) { |
134 | 133 |
$div = $('#' + params.id); |
135 | 134 |
} else { |
... | ... | |
140 | 139 |
$div.modal('open'); |
141 | 140 |
|
142 | 141 |
return true; |
142 |
}; |
|
143 |
|
|
144 |
/** |
|
145 |
* upload file to local storage for later sync |
|
146 |
* |
|
147 |
* should be used with P.M.file_upload(..., local=>1) |
|
148 |
*/ |
|
149 |
ns.LocalFileUpload = function(options) { |
|
150 |
this.storage_token = options.storage_token; // used in localstorage to retrieve the file |
|
151 |
this.dom_selector = options.dom_selector; // file inputs to listen on |
|
152 |
|
|
153 |
this.init(); |
|
154 |
}; |
|
155 |
|
|
156 |
ns.LocalFileUpload.prototype = { |
|
157 |
init: function() { |
|
158 |
$(this.dom_selector).change(this.handle_file_upload); |
|
159 |
}, |
|
160 |
handle_file_upload: function() { |
|
161 |
|
|
162 |
}, |
|
163 |
load_files: function() { |
|
164 |
return JSON.parse(localStorage.getImte(this.storage_token)); |
|
165 |
}, |
|
166 |
save_files: function() { |
|
167 |
return JSON.parse(localStorage.getImte(this.storage_token)); |
|
168 |
}, |
|
169 |
|
|
170 |
}; |
|
143 | 171 |
|
144 |
} |
|
145 | 172 |
}); |
package.json | ||
---|---|---|
1 |
{ |
|
2 |
"name": "dev", |
|
3 |
"version": "1.0.0", |
|
4 |
"description": "", |
|
5 |
"main": "index.js", |
|
6 |
"directories": { |
|
7 |
"doc": "doc" |
|
8 |
}, |
|
9 |
"scripts": { |
|
10 |
"test": "echo \"Error: no test specified\" && exit 1" |
|
11 |
}, |
|
12 |
"repository": { |
|
13 |
"type": "git", |
|
14 |
"url": "git+https://github.com/kivitendo/kivitendo-erp.git" |
|
15 |
}, |
|
16 |
"author": "", |
|
17 |
"license": "ISC", |
|
18 |
"bugs": { |
|
19 |
"url": "https://github.com/kivitendo/kivitendo-erp/issues" |
|
20 |
}, |
|
21 |
"homepage": "https://github.com/kivitendo/kivitendo-erp#readme" |
|
22 |
} |
templates/mobile_webpages/image_upload/local_list.html | ||
---|---|---|
1 |
[%- USE LxERP -%] [%- USE L %] |
|
2 |
[%- USE HTML %] |
|
3 |
[%- USE P %] |
|
4 |
[%- USE T8 %] |
|
5 |
|
|
6 |
<h4>[% source.title | html %]</h4> |
|
7 |
|
|
8 |
<div id="updoad_result"></div> |
|
9 |
|
|
10 |
<div id="stored-images" class="container"> |
|
11 |
</div> |
|
12 |
|
|
13 |
|
|
14 |
<div class="container"> |
|
15 |
<div class="row"> |
|
16 |
|
|
17 |
<div class="file-field input-field col"> |
|
18 |
<div class="btn m3 s12"> |
|
19 |
<span>[% 'Upload Image' | $T8 %]</span> |
|
20 |
<input |
|
21 |
name="uploadfiles[]" type="file" [% IF multiple %]multiple[% END %] |
|
22 |
id="upload_files" accept="[% SELF.accept_types %]" capture="camera" |
|
23 |
onchange="kivi.ImageUpload.add_files(this)"> |
|
24 |
</div> |
|
25 |
<div class="file-path-wrapper m9 s12"> |
|
26 |
<input class="file-path validate" type="hidden"> |
|
27 |
</div> |
|
28 |
</div> |
|
29 |
|
|
30 |
</div> |
|
31 |
|
|
32 |
<div class="row"> |
|
33 |
[% P.M.input_tag("object_number", "", label=LxERP.t8("Number")) %] |
|
34 |
[% P.M.button_tag("submit", LxERP.t8("Upload")) %] |
|
35 |
</div> |
|
36 |
|
|
37 |
|
|
38 |
</div> |
|
39 |
</div> |
|
40 |
|
|
41 |
<div id="upload_modal" class="modal"> |
|
42 |
<div class="modal-content"> |
|
43 |
<h4>Uploading</h4> |
|
44 |
|
|
45 |
<p>A bunch of text</p> |
|
46 |
</div> |
|
47 |
</div> |
Auch abrufbar als: Unified diff
ImageUpload: local storage erste Version