Implemented icon upload
parent
09b13e0b08
commit
22e93fb317
|
@ -1,4 +1,7 @@
|
||||||
# Changelog:
|
# Changelog:
|
||||||
|
* **24.05.19**
|
||||||
|
- Implemented icon upload
|
||||||
|
|
||||||
* **21.05.19**
|
* **21.05.19**
|
||||||
- Restructured project
|
- Restructured project
|
||||||
- Redesigned the audio input API (required for the client)
|
- Redesigned the audio input API (required for the client)
|
||||||
|
|
|
@ -147,4 +147,211 @@
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-icon-upload {
|
||||||
|
user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
|
||||||
|
min-width: 300px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.container-select {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.container-icons {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
width: min-content;
|
||||||
|
min-width: 150px;
|
||||||
|
min-height: 200px;
|
||||||
|
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
|
border: gray solid 1px;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
|
||||||
|
image {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin: 1px;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-buttons {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 2;
|
||||||
|
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.buttons-manage {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-upload {
|
||||||
|
margin-top: 5px;
|
||||||
|
border-top: 1px solid darkgray;
|
||||||
|
padding-top: 5px;
|
||||||
|
|
||||||
|
.container-error, .container-success {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 60px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
.error-message, .message {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
float: right;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-success {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-process {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
|
||||||
|
border: gray solid 1px;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
.upload-entry {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.container-icon {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
margin: 1px 1px 1px 4px;
|
||||||
|
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
|
||||||
|
vertical-align: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
margin: 2px 5px 2px 3px;
|
||||||
|
height: 16px;
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-message {
|
||||||
|
align-self: center;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
.modal-icon-upload {
|
||||||
|
.container-select {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.container-icons {
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-buttons {
|
||||||
|
max-width: unset;
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-manage {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
width: 50%;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-upload {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -207,7 +207,7 @@
|
||||||
|
|
||||||
<script class="jsrender-template" id="tmpl_select_info" type="text/html">
|
<script class="jsrender-template" id="tmpl_select_info" type="text/html">
|
||||||
<div class="select_info" style="width: 100%; max-width: 100%">
|
<div class="select_info" style="width: 100%; max-width: 100%">
|
||||||
<button type="button" class="close" aria-label="Close">
|
<button type="button" class="close button-modal-close" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="container-banner"></div>
|
<div class="container-banner"></div>
|
||||||
|
@ -242,11 +242,9 @@
|
||||||
<div class="modal-content" style="{{if full_size}}flex-grow: 1{{/if}}">
|
<div class="modal-content" style="{{if full_size}}flex-grow: 1{{/if}}">
|
||||||
<div class="modal-header {{if header_class}}{{:header_class}}{{/if}}">
|
<div class="modal-header {{if header_class}}{{:header_class}}{{/if}}">
|
||||||
<node key="modal_header"></node>
|
<node key="modal_header"></node>
|
||||||
{{if closeable}}
|
<button type="button" class="close button-modal-close" data-dismiss="modal" aria-label="Close">
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<node key="modal_body"></node>
|
<node key="modal_body"></node>
|
||||||
|
@ -267,11 +265,9 @@
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header modal-header-input">
|
<div class="modal-header modal-header-input">
|
||||||
<node key="modal_header"></node>
|
<node key="modal_header"></node>
|
||||||
{{if closeable}}
|
<button type="button" class="close button-modal-close" data-dismiss="modal" aria-label="Close">
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body modal-body-input">
|
<div class="modal-body modal-body-input">
|
||||||
<node key="question"></node>
|
<node key="question"></node>
|
||||||
|
@ -3097,7 +3093,7 @@
|
||||||
<div class="header">{{tr "Remote" /}}</div>
|
<div class="header">{{tr "Remote" /}}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="container-icons-list">
|
<div class="container-icons-list">
|
||||||
<div class="container-icons-remote {{if enable_select}}icon-select{{/if}}"></div>
|
<div class="container-icons-remote {{if enable_select || enable_delete}}icon-select{{/if}}"></div>
|
||||||
<div class="container-loading">
|
<div class="container-loading">
|
||||||
<a>{{tr "loading..." /}}</a>
|
<a>{{tr "loading..." /}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3108,12 +3104,14 @@
|
||||||
<a class="error-message">{{ŧr "An error occured" /}}</a>
|
<a class="error-message">{{ŧr "An error occured" /}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
|
||||||
<div class="container-buttons">
|
<div class="container-buttons">
|
||||||
|
{{if enable_upload}}
|
||||||
<button class="btn btn-success button-upload">{{tr "Upload" /}}</button>
|
<button class="btn btn-success button-upload">{{tr "Upload" /}}</button>
|
||||||
<button class="btn btn-danger button-upload">{{tr "Delete" /}}</button>
|
{{/if}}
|
||||||
|
{{if enable_delete}}
|
||||||
|
<button class="btn btn-danger button-delete">{{tr "Delete" /}}</button>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="group_box">
|
<div class="group_box">
|
||||||
|
@ -3136,6 +3134,38 @@
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script class="jsrender-template" id="tmpl_icon_upload" type="text/html">
|
||||||
|
<div class="modal-icon-upload">
|
||||||
|
<div class="container-select">
|
||||||
|
<div class="container-icons"> </div>
|
||||||
|
<div class="container-buttons">
|
||||||
|
<div class="buttons-manage">
|
||||||
|
<button class="btn btn-primary btn-raised button-add">{{tr "Add icon" /}}</button>
|
||||||
|
<button class="btn btn-danger button-remove">{{tr "Remove selected" /}}</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary btn-raised button-upload"></button>
|
||||||
|
|
||||||
|
<input type="file" class="input-file-upload" multiple/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container-upload">
|
||||||
|
<div class="container-error alert alert-danger">
|
||||||
|
<div class="error-message">You're not connected. Failed to upload icons</div>
|
||||||
|
<button type="button" class="btn btn-danger btn-raised button-upload-abort">{{tr "abort" /}}</button>
|
||||||
|
</div>
|
||||||
|
<div class="container-process"></div>
|
||||||
|
<div class="container-info">
|
||||||
|
<div class="container-info-uploaded">{{tr "Uploaded icons (total | successfully | error): " /}}</div>
|
||||||
|
<div class="uploaded-statistics"></div>
|
||||||
|
</div>
|
||||||
|
<div class="container-success alert alert-success">
|
||||||
|
<div class="message">Uploaded 10 icons successfully</div>
|
||||||
|
<button type="button" class="btn btn-success btn-raised button-upload-abort">{{tr "okey" /}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
<script class="jsrender-template" id="tmpl_avatar_list" type="text/html">
|
<script class="jsrender-template" id="tmpl_avatar_list" type="text/html">
|
||||||
<div class="modal-avatar-list">
|
<div class="modal-avatar-list">
|
||||||
<div class="container-list">
|
<div class="container-list">
|
||||||
|
|
|
@ -56,8 +56,8 @@ namespace transfer {
|
||||||
export function spawn_download_transfer(key: DownloadKey) : DownloadTransfer {
|
export function spawn_download_transfer(key: DownloadKey) : DownloadTransfer {
|
||||||
return new RequestFileDownload(key);
|
return new RequestFileDownload(key);
|
||||||
}
|
}
|
||||||
export function spawn_upload_transfer(key: DownloadKey) : DownloadTransfer {
|
export function spawn_upload_transfer(key: UploadKey) : RequestFileUpload {
|
||||||
return new RequestFileDownload(key);
|
return new RequestFileUpload(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,6 +353,28 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
(transfer["_callback"] as (val: transfer.UploadKey) => void)(transfer);
|
(transfer["_callback"] as (val: transfer.UploadKey) => void)(transfer);
|
||||||
this.pending_upload_requests.remove(transfer);
|
this.pending_upload_requests.remove(transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** File management **/
|
||||||
|
async delete_file(props: {
|
||||||
|
name: string,
|
||||||
|
path?: string;
|
||||||
|
cid?: number;
|
||||||
|
cpw?: string;
|
||||||
|
}) : Promise<void> {
|
||||||
|
if(!props.name)
|
||||||
|
throw "invalid name!";
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.handle.serverConnection.send_command("ftdeletefile", {
|
||||||
|
cid: props.cid || 0,
|
||||||
|
cpw: props.cpw,
|
||||||
|
path: props.path || "",
|
||||||
|
name: props.name
|
||||||
|
})
|
||||||
|
} catch(error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Icon {
|
class Icon {
|
||||||
|
@ -386,8 +408,19 @@ function media_image_type(type: ImageType, file?: boolean) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function image_type(base64: string) {
|
function image_type(base64: string | ArrayBuffer) {
|
||||||
const bin = atob(base64);
|
const ab2str10 = () => {
|
||||||
|
const buf = new Uint8Array(base64 as ArrayBuffer);
|
||||||
|
if(buf.byteLength < 10)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
let result = "";
|
||||||
|
for(let index = 0; index < 10; index++)
|
||||||
|
result += String.fromCharCode(buf[index]);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bin = typeof(base64) === "string" ? base64 : ab2str10();
|
||||||
if(bin.length < 10) return ImageType.UNKNOWN;
|
if(bin.length < 10) return ImageType.UNKNOWN;
|
||||||
|
|
||||||
if(bin[0] == String.fromCharCode(66) && bin[1] == String.fromCharCode(77)) {
|
if(bin[0] == String.fromCharCode(66) && bin[1] == String.fromCharCode(77)) {
|
||||||
|
@ -470,6 +503,15 @@ class IconManager {
|
||||||
IconManager.cache = new CacheManager("icons");
|
IconManager.cache = new CacheManager("icons");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async delete_icon(id: number) : Promise<void> {
|
||||||
|
if(id <= 1000)
|
||||||
|
throw "invalid id!";
|
||||||
|
|
||||||
|
await this.handle.delete_file({
|
||||||
|
name: '/icon_' + id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
iconList() : Promise<FileEntry[]> {
|
iconList() : Promise<FileEntry[]> {
|
||||||
return this.handle.requestFileList("/icons");
|
return this.handle.requestFileList("/icons");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
enum ErrorID {
|
enum ErrorID {
|
||||||
PERMISSION_ERROR = 2568,
|
PERMISSION_ERROR = 2568,
|
||||||
EMPTY_RESULT = 0x0501,
|
EMPTY_RESULT = 0x0501,
|
||||||
PLAYLIST_IS_IN_USE = 0x2103
|
PLAYLIST_IS_IN_USE = 0x2103,
|
||||||
|
|
||||||
|
FILE_ALREADY_EXISTS = 2050,
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommandResult {
|
class CommandResult {
|
||||||
|
|
|
@ -84,6 +84,11 @@ namespace i18n {
|
||||||
return translated;
|
return translated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function tra(message: string, ...args: any[]) {
|
||||||
|
message = tr(message);
|
||||||
|
return MessageHelper.formatMessage(message, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
async function load_translation_file(url: string, path: string) : Promise<TranslationFile> {
|
async function load_translation_file(url: string, path: string) : Promise<TranslationFile> {
|
||||||
return new Promise<TranslationFile>((resolve, reject) => {
|
return new Promise<TranslationFile>((resolve, reject) => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
@ -296,4 +301,5 @@ namespace i18n {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const tr: typeof i18n.tr = i18n.tr;
|
const tr: typeof i18n.tr = i18n.tr;
|
||||||
|
const tra: typeof i18n.tra = i18n.tra;
|
|
@ -541,6 +541,7 @@ const loader_javascript = {
|
||||||
"js/crypto/sha.js",
|
"js/crypto/sha.js",
|
||||||
"js/crypto/hex.js",
|
"js/crypto/hex.js",
|
||||||
"js/crypto/asn1.js",
|
"js/crypto/asn1.js",
|
||||||
|
"js/crypto/crc32.js",
|
||||||
|
|
||||||
//load the profiles
|
//load the profiles
|
||||||
"js/profiles/ConnectionProfile.js",
|
"js/profiles/ConnectionProfile.js",
|
||||||
|
|
|
@ -326,6 +326,7 @@ function main() {
|
||||||
password: password,
|
password: password,
|
||||||
hashed: password_hashed
|
hashed: password_hashed
|
||||||
} : undefined);
|
} : undefined);
|
||||||
|
Modals.spawnIconUpload(connection);
|
||||||
} else {
|
} else {
|
||||||
Modals.spawnConnectModal({
|
Modals.spawnConnectModal({
|
||||||
url: address,
|
url: address,
|
||||||
|
@ -336,6 +337,7 @@ function main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const task_teaweb_starter: loader.Task = {
|
const task_teaweb_starter: loader.Task = {
|
||||||
|
|
|
@ -85,6 +85,7 @@ class Modal {
|
||||||
shown: boolean;
|
shown: boolean;
|
||||||
|
|
||||||
close_listener: (() => any)[] = [];
|
close_listener: (() => any)[] = [];
|
||||||
|
close_elements: JQuery;
|
||||||
|
|
||||||
constructor(props: ModalProperties) {
|
constructor(props: ModalProperties) {
|
||||||
this.properties = props;
|
this.properties = props;
|
||||||
|
@ -117,7 +118,8 @@ class Modal {
|
||||||
Object.assign(properties, this.properties.template_properties);
|
Object.assign(properties, this.properties.template_properties);
|
||||||
|
|
||||||
const tag = template.renderTag(properties);
|
const tag = template.renderTag(properties);
|
||||||
|
this.close_elements = tag.find(".button-modal-close");
|
||||||
|
this.close_elements.toggle(this.properties.closeable);
|
||||||
this._htmlTag = tag;
|
this._htmlTag = tag;
|
||||||
this._htmlTag.on('hide.bs.modal', event => !this.properties.closeable || this.close());
|
this._htmlTag.on('hide.bs.modal', event => !this.properties.closeable || this.close());
|
||||||
this._htmlTag.on('hidden.bs.modal', event => this._htmlTag.detach());
|
this._htmlTag.on('hidden.bs.modal', event => this._htmlTag.detach());
|
||||||
|
@ -145,6 +147,14 @@ class Modal {
|
||||||
for(const listener of this.close_listener)
|
for(const listener of this.close_listener)
|
||||||
listener();
|
listener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_closeable(flag: boolean) {
|
||||||
|
if(flag === this.properties.closeable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.properties.closeable = flag;
|
||||||
|
this.close_elements.toggle(flag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createModal(data: ModalProperties | any) : Modal {
|
function createModal(data: ModalProperties | any) : Modal {
|
||||||
|
|
|
@ -6,13 +6,17 @@ namespace Modals {
|
||||||
//TODO Upload/delete button
|
//TODO Upload/delete button
|
||||||
export function spawnIconSelect(client: ConnectionHandler, callback_icon?: (id: number) => any, selected_icon?: number) {
|
export function spawnIconSelect(client: ConnectionHandler, callback_icon?: (id: number) => any, selected_icon?: number) {
|
||||||
selected_icon = selected_icon || 0;
|
selected_icon = selected_icon || 0;
|
||||||
|
let allow_manage = client.permissions.neededPermission(PermissionType.B_ICON_MANAGE).granted(1);
|
||||||
|
|
||||||
const modal = createModal({
|
const modal = createModal({
|
||||||
header: tr("Icons"),
|
header: tr("Icons"),
|
||||||
footer: undefined,
|
footer: undefined,
|
||||||
body: () => {
|
body: () => {
|
||||||
const template = $("#tmpl_icon_select").renderTag({
|
const template = $("#tmpl_icon_select").renderTag({
|
||||||
enable_select: !!callback_icon
|
enable_select: !!callback_icon,
|
||||||
|
|
||||||
|
enable_upload: allow_manage,
|
||||||
|
enable_delete: allow_manage
|
||||||
});
|
});
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
|
@ -20,6 +24,8 @@ namespace Modals {
|
||||||
});
|
});
|
||||||
|
|
||||||
const button_select = modal.htmlTag.find(".button-select");
|
const button_select = modal.htmlTag.find(".button-select");
|
||||||
|
const button_delete = modal.htmlTag.find(".button-delete").prop("disabled", true);
|
||||||
|
const button_upload = modal.htmlTag.find(".button-upload").prop("disabled", !allow_manage);
|
||||||
|
|
||||||
const container_loading = modal.htmlTag.find(".container-loading").hide();
|
const container_loading = modal.htmlTag.find(".container-loading").hide();
|
||||||
const container_no_permissions = modal.htmlTag.find(".container-no-permissions").hide();
|
const container_no_permissions = modal.htmlTag.find(".container-no-permissions").hide();
|
||||||
|
@ -90,7 +96,7 @@ namespace Modals {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const tag = client.fileManager.icons.generateTag(icon_id, {animate: false}).attr('title', "Icon " + icon_id);
|
const tag = client.fileManager.icons.generateTag(icon_id, {animate: false}).attr('title', "Icon " + icon_id);
|
||||||
if(callback_icon) {
|
if(callback_icon || allow_manage) {
|
||||||
tag.on('click', event => {
|
tag.on('click', event => {
|
||||||
container_icons.find(".selected").removeClass("selected");
|
container_icons.find(".selected").removeClass("selected");
|
||||||
tag.addClass("selected");
|
tag.addClass("selected");
|
||||||
|
@ -98,8 +104,12 @@ namespace Modals {
|
||||||
selected_container.empty().append(tag.clone());
|
selected_container.empty().append(tag.clone());
|
||||||
selected_icon = icon_id;
|
selected_icon = icon_id;
|
||||||
button_select.prop("disabled", false);
|
button_select.prop("disabled", false);
|
||||||
|
button_delete.prop("disabled", !allow_manage);
|
||||||
});
|
});
|
||||||
tag.on('dblclick', event => {
|
tag.on('dblclick', event => {
|
||||||
|
if(!callback_icon)
|
||||||
|
return;
|
||||||
|
|
||||||
callback_icon(icon_id);
|
callback_icon(icon_id);
|
||||||
modal.close();
|
modal.close();
|
||||||
});
|
});
|
||||||
|
@ -127,6 +137,31 @@ namespace Modals {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
button_delete.on('click', event => {
|
||||||
|
if(!selected_icon)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const selected = modal.htmlTag.find(".selected");
|
||||||
|
if(selected.length != 1)
|
||||||
|
console.warn(tr("UI selected icon length does not equal with 1! (%o)"), selected.length);
|
||||||
|
|
||||||
|
if(selected_icon < 1000) return; /* we cant delete local icons */
|
||||||
|
|
||||||
|
client.fileManager.icons.delete_icon(selected_icon).then(() => {
|
||||||
|
selected.detach();
|
||||||
|
}).catch(error => {
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.PERMISSION_ERROR)
|
||||||
|
return;
|
||||||
|
console.warn(tr("Failed to delete icon %d: %o"), selected_icon, error);
|
||||||
|
|
||||||
|
error = error instanceof CommandResult ? error.extra_message || error.message : error;
|
||||||
|
|
||||||
|
createErrorModal(tr("Failed to delete icon"), tra("Failed to delete icon.<br>Error: ", error)).open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
button_upload.on('click', event => spawnIconUpload(client));
|
||||||
|
|
||||||
update_local_icons([100, 200, 300, 500, 600]);
|
update_local_icons([100, 200, 300, 500, 600]);
|
||||||
update_remote_icons();
|
update_remote_icons();
|
||||||
modal.htmlTag.find('.button-reload').on('click', () => update_remote_icons());
|
modal.htmlTag.find('.button-reload').on('click', () => update_remote_icons());
|
||||||
|
@ -140,4 +175,465 @@ namespace Modals {
|
||||||
});
|
});
|
||||||
modal.open();
|
modal.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UploadingIcon {
|
||||||
|
file: File;
|
||||||
|
state: "loading" | "valid" | "error";
|
||||||
|
upload_state: "unset" | "uploading" | "uploaded" | "error";
|
||||||
|
|
||||||
|
html_tag?: JQuery;
|
||||||
|
image_element?: () => HTMLImageElement;
|
||||||
|
|
||||||
|
loader: Promise<void>;
|
||||||
|
|
||||||
|
upload_icon: () => () => Promise<void>;
|
||||||
|
upload_html_tag?: JQuery;
|
||||||
|
|
||||||
|
icon_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_icon_upload(file: File, client: ConnectionHandler) : UploadingIcon {
|
||||||
|
const icon = {} as UploadingIcon;
|
||||||
|
icon.file = file;
|
||||||
|
icon.upload_state = "unset";
|
||||||
|
|
||||||
|
const file_too_big = () => {
|
||||||
|
console.error(tr("Failed to load file %s: File is too big!"), file.name);
|
||||||
|
createErrorModal(tr("Icon upload failed"), tra("Failed to upload icon {}.<br>The given file is too big!", file.name)).open();
|
||||||
|
icon.state = "error";
|
||||||
|
};
|
||||||
|
if(file.size > 1024 * 1024 * 512) {
|
||||||
|
file_too_big();
|
||||||
|
} else if((file.size | 0) <= 0) {
|
||||||
|
console.error(tr("Failed to load file %s: Your browser does not support file sizes!"), file.name);
|
||||||
|
createErrorModal(tr("Icon upload failed"), tra("Failed to upload icon {}.<br>Your browser does not support file sizes!", file.name)).open();
|
||||||
|
icon.state = "error";
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
icon.state = "loading";
|
||||||
|
icon.loader = (async () => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
reader.onload = resolve;
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
} catch(error) {
|
||||||
|
console.log("Image failed to load (%o)", error);
|
||||||
|
console.error(tr("Failed to load file %s: Image failed to load"), file.name);
|
||||||
|
createErrorModal(tr("Icon upload failed"), tra("Failed to upload icon {}.<br>Failed to load image", file.name)).open();
|
||||||
|
icon.state = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = reader.result as string;
|
||||||
|
if(typeof(result) !== "string") {
|
||||||
|
console.error(tr("Failed to load file %s: Result is not an media string (%o)"), file.name, result);
|
||||||
|
createErrorModal(tr("Icon upload failed"), tra("Failed to upload icon {}.<br>Result is not an media string", file.name)).open();
|
||||||
|
icon.state = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* get the CRC32 sum */
|
||||||
|
{
|
||||||
|
if(!result.startsWith("data:image/")) {
|
||||||
|
console.error(tr("Failed to load file %s: Invalid data media type (%o)"), file.name, result);
|
||||||
|
createErrorModal(tr("Icon upload failed"), tra("Failed to upload icon {}.<br>File is not an image", file.name)).open();
|
||||||
|
icon.state = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const semi = result.indexOf(';');
|
||||||
|
const type = result.substring(11, semi);
|
||||||
|
console.log(tr("Given image has type %s"), type);
|
||||||
|
if(!result.substr(semi + 1).startsWith("base64,")) {
|
||||||
|
console.error(tr("Failed to load file %s: Mimetype isnt base64 encoded (%o)"), file.name, result.substr(semi + 1));
|
||||||
|
createErrorModal(tr("Icon upload failed"), tra("Failed to upload icon {}.<br>Decoder returned unknown result", file.name)).open();
|
||||||
|
icon.state = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const crc = new Crc32();
|
||||||
|
crc.update(arrayBufferBase64(result.substr(semi + 8)));
|
||||||
|
icon.icon_id = crc.digest(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const image = document.createElement("img");
|
||||||
|
try {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
image.onload = resolve;
|
||||||
|
image.onerror = reject;
|
||||||
|
image.src = result;
|
||||||
|
});
|
||||||
|
} catch(error) {
|
||||||
|
console.log("Image failed to load (%o)", error);
|
||||||
|
console.error(tr("Failed to load file %s: Image failed to load"), file.name);
|
||||||
|
createErrorModal(tr("Icon upload failed"), tra("Failed to upload icon {}.<br>Failed to load image", file.name)).open();
|
||||||
|
icon.state = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const width_error = message => {
|
||||||
|
console.error(tr("Failed to load file %s: Invalid bounds: %s"), file.name, message);
|
||||||
|
createErrorModal(tr("Icon upload failed"), tra("Failed to upload icon {}.<br>Image is too large ({})", file.name, message)).open();
|
||||||
|
icon.state = "error";
|
||||||
|
};
|
||||||
|
|
||||||
|
if(image.naturalWidth > 32 && image.naturalHeight > 32) {
|
||||||
|
width_error("width and height (max 32px)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(image.naturalWidth > 32) {
|
||||||
|
width_error("width (max 32px)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(image.naturalHeight > 32) {
|
||||||
|
width_error("height (max 32px)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Image loaded (%dx%d) %s (%s)", image.naturalWidth, image.naturalHeight, image.name, icon.icon_id);
|
||||||
|
icon.image_element = () => {
|
||||||
|
const image = document.createElement("img");
|
||||||
|
image.src = result;
|
||||||
|
return image;
|
||||||
|
};
|
||||||
|
icon.state = "valid";
|
||||||
|
})();
|
||||||
|
|
||||||
|
icon.upload_icon = () => {
|
||||||
|
const create_progress_bar = () => {
|
||||||
|
const html = $.spawn("div").addClass("progress");
|
||||||
|
const indicator = $.spawn("div").addClass("progress-bar bg-success progress-bar-striped progress-bar-animated");
|
||||||
|
const message = $.spawn("div").addClass("progress-message");
|
||||||
|
const set_value = value => {
|
||||||
|
indicator.stop(true, false).animate({width: value + "%"}, 250);
|
||||||
|
if(value === 100)
|
||||||
|
setTimeout(() => indicator.removeClass("progress-bar-striped progress-bar-animated"), 900)
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
html_tag: html.append(indicator).append(message),
|
||||||
|
set_value: set_value,
|
||||||
|
set_message: msg => message.text(msg),
|
||||||
|
set_error: msg => {
|
||||||
|
message.text(tr("error: ") + msg);
|
||||||
|
set_value(100);
|
||||||
|
indicator.removeClass("bg-success").addClass("bg-danger");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const container_image = $.spawn("div").addClass("container-icon");
|
||||||
|
const bar = create_progress_bar();
|
||||||
|
|
||||||
|
const set_error = message => {
|
||||||
|
bar.set_value(100);
|
||||||
|
bar.set_message(tr("error: ") + message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const html_tag = $.spawn("div")
|
||||||
|
.addClass("upload-entry")
|
||||||
|
.append(container_image)
|
||||||
|
.append(bar.html_tag);
|
||||||
|
|
||||||
|
icon.upload_html_tag = html_tag;
|
||||||
|
|
||||||
|
let icon_added = false;
|
||||||
|
if(icon.image_element) {
|
||||||
|
container_image.append(icon.image_element());
|
||||||
|
icon_added = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bar.set_value(0);
|
||||||
|
bar.set_value(tr("waiting"));
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
const time_begin = Date.now();
|
||||||
|
|
||||||
|
if(icon.state === "loading") {
|
||||||
|
bar.set_message(tr("Awaiting local processing"));
|
||||||
|
await icon.loader;
|
||||||
|
// @ts-ignore Could happen because the loader function updates the state
|
||||||
|
if(icon.state !== "valid") {
|
||||||
|
set_error(tr("local processing failed"));
|
||||||
|
icon.upload_state = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if(icon.state === "error") {
|
||||||
|
set_error(tr("local processing error"));
|
||||||
|
icon.upload_state = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!icon_added)
|
||||||
|
container_image.append(icon.image_element());
|
||||||
|
|
||||||
|
bar.set_value(25);
|
||||||
|
bar.set_message(tr("initializing"));
|
||||||
|
|
||||||
|
let upload_key: transfer.UploadKey;
|
||||||
|
try {
|
||||||
|
upload_key = await client.fileManager.upload_file({
|
||||||
|
channel: undefined,
|
||||||
|
channel_password: undefined,
|
||||||
|
name: '/icon_' + icon.icon_id,
|
||||||
|
overwrite: false,
|
||||||
|
path: '',
|
||||||
|
size: icon.file.size
|
||||||
|
})
|
||||||
|
} catch(error) {
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.FILE_ALREADY_EXISTS) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500 + Math.floor(Math.random() * 500)));
|
||||||
|
bar.set_message(tr("icon already exists"));
|
||||||
|
bar.set_value(100);
|
||||||
|
icon.upload_state = "uploaded";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error(tr("Failed to initialize upload: %o"), error);
|
||||||
|
bar.set_error(tr("failed to initialize upload"));
|
||||||
|
icon.upload_state = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bar.set_value(50);
|
||||||
|
bar.set_message(tr("uploading"));
|
||||||
|
|
||||||
|
const connection = transfer.spawn_upload_transfer(upload_key);
|
||||||
|
try {
|
||||||
|
await connection.put_data(icon.file)
|
||||||
|
} catch(error) {
|
||||||
|
console.error(tr("Icon upload failed for icon %s: %o"), icon.file.name, error);
|
||||||
|
if(typeof(error) === "string")
|
||||||
|
bar.set_error(tr("upload failed: ") + error);
|
||||||
|
else
|
||||||
|
bar.set_error(tr("upload failed"));
|
||||||
|
icon.upload_state = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const time_end = Date.now();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, Math.max(0, 1000 - (time_end - time_begin))));
|
||||||
|
bar.set_value(100);
|
||||||
|
bar.set_message(tr("upload completed"));
|
||||||
|
icon.upload_state = "uploaded";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function spawnIconUpload(client: ConnectionHandler) {
|
||||||
|
const modal = createModal({
|
||||||
|
header: tr("Upload Icons"),
|
||||||
|
footer: undefined,
|
||||||
|
body: () => {
|
||||||
|
const template = $("#tmpl_icon_upload").renderTag();
|
||||||
|
return template;
|
||||||
|
},
|
||||||
|
closeable: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const button_upload = modal.htmlTag.find(".button-upload");
|
||||||
|
const button_delete = modal.htmlTag.find(".button-remove").prop("disabled", true);
|
||||||
|
const button_add = modal.htmlTag.find(".button-add");
|
||||||
|
const button_upload_abort = modal.htmlTag.find(".button-upload-abort");
|
||||||
|
const input_file = modal.htmlTag.find(".input-file-upload") as JQuery<HTMLInputElement>;
|
||||||
|
const container_icons = modal.htmlTag.find(".container-icons");
|
||||||
|
|
||||||
|
let selected_icon: UploadingIcon;
|
||||||
|
let icons: UploadingIcon[] = [];
|
||||||
|
|
||||||
|
const update_upload_button = () => {
|
||||||
|
const icon_count = icons.filter(e => e.state === "valid").length;
|
||||||
|
button_upload.empty();
|
||||||
|
tra("Upload icons ({})", icon_count).forEach(e => e.appendTo(button_upload));
|
||||||
|
button_upload.prop("disabled", icon_count == 0);
|
||||||
|
};
|
||||||
|
update_upload_button();
|
||||||
|
|
||||||
|
const add_icon = (icon: UploadingIcon) => {
|
||||||
|
icons.push(icon);
|
||||||
|
icon.loader.then(e => {
|
||||||
|
if(icon.state === "valid") {
|
||||||
|
const image = icon.image_element();
|
||||||
|
const element = $.spawn("div")
|
||||||
|
.addClass("icon-container")
|
||||||
|
.append(image);
|
||||||
|
container_icons.append(icon.html_tag = element);
|
||||||
|
|
||||||
|
element.on('click', event => {
|
||||||
|
container_icons.find(".selected").removeClass("selected");
|
||||||
|
element.addClass("selected");
|
||||||
|
|
||||||
|
selected_icon = icon;
|
||||||
|
button_delete.prop("disabled", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
update_upload_button();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
button_delete.on('click', event => {
|
||||||
|
if(!selected_icon)
|
||||||
|
return;
|
||||||
|
icons = icons.filter(e => e !== selected_icon);
|
||||||
|
if(selected_icon.html_tag)
|
||||||
|
selected_icon.html_tag.detach();
|
||||||
|
button_delete.prop("disabled", true);
|
||||||
|
update_upload_button();
|
||||||
|
});
|
||||||
|
|
||||||
|
button_add.on('click', event => input_file.click());
|
||||||
|
input_file.on('change', event => {
|
||||||
|
if(input_file[0].files.length > 0) {
|
||||||
|
for(let index = 0; index < input_file[0].files.length; index++) {
|
||||||
|
const file = input_file[0].files.item(index);
|
||||||
|
{
|
||||||
|
let duplicate = false;
|
||||||
|
|
||||||
|
for(const icon of icons)
|
||||||
|
if(icon.file.name === file.name && icon.file.lastModified === file.lastModified && icon.state !== "error") {
|
||||||
|
duplicate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(duplicate)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_icon(handle_icon_upload(file, client));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
container_icons.on('dragover', ((event: DragEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
event.dataTransfer.dropEffect = 'copy';
|
||||||
|
}) as any);
|
||||||
|
container_icons.on('drop', ((event: DragEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
for(let index = 0; index < event.dataTransfer.files.length; index++) {
|
||||||
|
const file = event.dataTransfer.files.item(index);
|
||||||
|
{
|
||||||
|
let duplicate = false;
|
||||||
|
|
||||||
|
for(const icon of icons)
|
||||||
|
if(icon.file === file && icon.state !== "error") {
|
||||||
|
duplicate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(duplicate)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_icon(handle_icon_upload(file, client));
|
||||||
|
}
|
||||||
|
}) as any);
|
||||||
|
|
||||||
|
/* upload process */
|
||||||
|
{
|
||||||
|
const container_upload = modal.htmlTag.find(".container-upload");
|
||||||
|
const container_error = container_upload.find(".container-error");
|
||||||
|
const container_success = container_upload.find(".container-success");
|
||||||
|
const container_process = container_upload.find(".container-process");
|
||||||
|
const container_info = container_upload.find(".container-info");
|
||||||
|
const container_statistics = container_upload.find(".uploaded-statistics");
|
||||||
|
|
||||||
|
const show_critical_error = message => {
|
||||||
|
container_error.find(".error-message").text(message);
|
||||||
|
container_error.show();
|
||||||
|
};
|
||||||
|
|
||||||
|
const finish_upload = () => {
|
||||||
|
icons = icons.filter(e => {
|
||||||
|
if(e.upload_state === "uploaded") {
|
||||||
|
e.html_tag.detach();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
update_upload_button();
|
||||||
|
button_upload.prop("disabled", false);
|
||||||
|
button_upload.prop("disabled", false);
|
||||||
|
container_upload.hide();
|
||||||
|
container_error.hide();
|
||||||
|
modal.set_closeable(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const execute_upload = async () => {
|
||||||
|
if(!client || !client.fileManager) {
|
||||||
|
show_critical_error(tr("Invalid client handle"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!client.connected) {
|
||||||
|
show_critical_error(tr("Not connected"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invoke_count = 0;
|
||||||
|
let succeed_count = 0;
|
||||||
|
let failed_count = 0;
|
||||||
|
|
||||||
|
const uploads = icons.filter(e => e.state !== "error");
|
||||||
|
|
||||||
|
const executes: {icon: UploadingIcon, task: () => Promise<void>}[] = [];
|
||||||
|
for(const icon of uploads) {
|
||||||
|
executes.push({
|
||||||
|
icon: icon,
|
||||||
|
task: icon.upload_icon()
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!icon.upload_html_tag)
|
||||||
|
continue; /* TODO: error? */
|
||||||
|
icon.upload_html_tag.appendTo(container_process);
|
||||||
|
}
|
||||||
|
|
||||||
|
const update_state = () => container_statistics.text(invoke_count + " | " + succeed_count + " | " + failed_count);
|
||||||
|
for(const execute of executes) {
|
||||||
|
invoke_count++;
|
||||||
|
update_state();
|
||||||
|
try {
|
||||||
|
await execute.task();
|
||||||
|
if(execute.icon.upload_state !== "uploaded")
|
||||||
|
throw "failed";
|
||||||
|
succeed_count++;
|
||||||
|
} catch(error) {
|
||||||
|
failed_count++;
|
||||||
|
}
|
||||||
|
update_state();
|
||||||
|
}
|
||||||
|
container_info.css({opacity: 1}).animate({opacity: 0}, 250, () => container_info.css({opacity: undefined}).hide());
|
||||||
|
container_success.find(".message").html(
|
||||||
|
"Total icons: " + invoke_count + "<br>" +
|
||||||
|
"Succeeded icons: " + succeed_count + "<br>" +
|
||||||
|
"Failed icons: " + failed_count
|
||||||
|
);
|
||||||
|
container_success.css({opacity: 0}).show().animate({opacity: 1}, 250, () => container_success.css({opacity: undefined}));
|
||||||
|
};
|
||||||
|
|
||||||
|
button_upload.on('click', event => {
|
||||||
|
modal.set_closeable(false);
|
||||||
|
button_upload.prop("disabled", true);
|
||||||
|
button_delete.prop("disabled", true);
|
||||||
|
button_add.prop("disabled", true);
|
||||||
|
container_process.empty();
|
||||||
|
container_upload.show();
|
||||||
|
execute_upload();
|
||||||
|
});
|
||||||
|
|
||||||
|
button_upload_abort.on('click', event => finish_upload());
|
||||||
|
|
||||||
|
container_success.hide();
|
||||||
|
container_upload.hide();
|
||||||
|
container_error.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.open();
|
||||||
|
modal.set_closeable(true);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -79,7 +79,11 @@ class RecorderProfile {
|
||||||
async initialize() : Promise<void> {
|
async initialize() : Promise<void> {
|
||||||
await this.load();
|
await this.load();
|
||||||
await this.reinitialize_filter();
|
await this.reinitialize_filter();
|
||||||
await this.input.start();
|
try {
|
||||||
|
await this.input.start();
|
||||||
|
} catch(error) {
|
||||||
|
console.warn(tr("Failed to start recorder after initialize (%o)"), error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private initialize_input() {
|
private initialize_input() {
|
||||||
|
@ -96,6 +100,7 @@ class RecorderProfile {
|
||||||
this.callback_stop();
|
this.callback_stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
this.input.callback_state_change = () => {
|
this.input.callback_state_change = () => {
|
||||||
const new_state = this.input.current_state() === audio.recorder.InputState.RECORDING || this.input.current_state() === audio.recorder.InputState.DRY;
|
const new_state = this.input.current_state() === audio.recorder.InputState.RECORDING || this.input.current_state() === audio.recorder.InputState.DRY;
|
||||||
|
|
||||||
|
@ -106,6 +111,7 @@ class RecorderProfile {
|
||||||
if(this.callback_support_change)
|
if(this.callback_support_change)
|
||||||
this.callback_support_change();
|
this.callback_support_change();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private async load() {
|
private async load() {
|
||||||
|
|
Loading…
Reference in New Issue