Make class methods actual class methods instead of setting them in the constructor

This commit is contained in:
Max Berger
2026-03-28 23:37:12 +01:00
parent 7766c80c6a
commit e631734e6e
8 changed files with 1326 additions and 1284 deletions

View File

@@ -45,251 +45,259 @@ export class CollectionsScene {
* scene is popped.
*/
constructor(user, password, principal_collection, onerror) {
/** @type {HTMLElement} */ let html_scene = get_element_by_id("collectionsscene");
/** @type {HTMLElement} */ let template = get_element(html_scene, "[data-name=collectiontemplate]");
/** @type {HTMLElement} */ let new_btn = get_element(html_scene, "[data-name=new]");
/** @type {HTMLElement} */ let upload_btn = get_element(html_scene, "[data-name=upload]");
/** @type {HTMLElement} */ let incomingshares_btn = get_element(html_scene, "[data-name=incomingshares]");
/** @type {HTMLElement} */ let error_div = get_element(html_scene, "[data-name=collectionsscene_error]");
this._user = user;
this._password = password;
this._principal_collection = principal_collection;
this._onerror = onerror;
/** @type {Array<HTMLElement>} */ let nodes = [];
let errorHandler = new ErrorHandler(error_div);
this._html_scene = get_element_by_id("collectionsscene");
this._template = get_element(this._html_scene, "[data-name=collectiontemplate]");
this._new_btn = get_element(this._html_scene, "[data-name=new]");
this._upload_btn = get_element(this._html_scene, "[data-name=upload]");
this._incomingshares_btn = get_element(this._html_scene, "[data-name=incomingshares]");
this._error_div = get_element(this._html_scene, "[data-name=collectionsscene_error]");
function onnew() {
try {
let create_collection_scene = new CreateEditCollectionScene(user, password, principal_collection);
push_scene(create_collection_scene);
} catch (err) {
console.error(err);
}
return false;
}
function onupload() {
try {
let upload_scene = new UploadCollectionScene(user, password, principal_collection);
push_scene(upload_scene);
} catch (err) {
console.error(err);
}
return false;
}
function onincomingshares() {
try {
let incoming_sharing_scene = new IncomingSharingScene(user, password);
push_scene(incoming_sharing_scene);
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {Collection} collection
*/
function onedit(collection) {
try {
let edit_collection_scene = new CreateEditCollectionScene(user, password, collection);
push_scene(edit_collection_scene);
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {Collection} collection
*/
function onshare(collection) {
try {
let share_collection_scene = new ShareCollectionScene(user, password, collection);
push_scene(share_collection_scene);
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {Collection} collection
*/
function ondelete(collection) {
try {
let delete_collection_scene = new DeleteConfirmationScene(
user, password, "Delete Collection", collection, collection.displayname || collection.href,
delete_collection, true
);
push_scene(delete_collection_scene);
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {any[]} collections
* @param {import("../api/sharing.js").Share[]} shares
* @param {boolean} clear_error
*/
function show_collections(collections, shares, clear_error) {
/** @type {HTMLElement} */ let navBar = get_element(document, "#logoutview");
let heightOfNavBar = navBar.offsetHeight + "px";
html_scene.style.marginTop = heightOfNavBar;
html_scene.style.height = "calc(100vh - " + heightOfNavBar + ")";
if (clear_error) {
errorHandler.clearError();
}
// Clear old nodes
nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = [];
collections.forEach(function (/** @type {Collection} */ collection) {
/** @type {HTMLElement} */ let node = /** @type {HTMLElement} */(template.cloneNode(true));
node.classList.remove("hidden");
/** @type {HTMLElement} */ let title_form = get_element(node, "[data-name=title]");
/** @type {HTMLElement} */ let description_form = get_element(node, "[data-name=description]");
/** @type {HTMLElement} */ let contentcount_form = get_element(node, "[data-name=contentcount]");
/** @type {HTMLInputElement} */ let url_form = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=url]"));
/** @type {HTMLElement} */ let color_form = get_element(node, "[data-name=color]");
/** @type {HTMLElement} */ let delete_btn = get_element(node, "[data-name=delete]");
/** @type {HTMLElement} */ let edit_btn = get_element(node, "[data-name=edit]");
/** @type {HTMLElement} */ let share_btn = get_element(node, "[data-name=share]");
/** @type {HTMLAnchorElement} */ let download_btn = /** @type {HTMLAnchorElement} */ (get_element(node, "[data-name=download]"));
if (collection.color) {
color_form.style.background = collection.color;
}
let possible_types = [CollectionType.ADDRESSBOOK, CollectionType.WEBCAL];
[CollectionType.CALENDAR, ""].forEach(function (e) {
[CollectionType.union(e, CollectionType.JOURNAL), e].forEach(function (e) {
[CollectionType.union(e, CollectionType.TASKS), e].forEach(function (e) {
if (e) {
possible_types.push(e);
}
});
});
});
possible_types.forEach(function (e) {
if (e !== collection.type) {
get_element(node, "[data-name=" + e + "]").classList.add("hidden");
}
});
let share_info = get_element(node, "[data-name=shared-by]");
let transformed_from = get_element(node, "[data-name=transformed-from]");
let share = (shares || []).find(
s => (s.ShareType === "map") &&
(s.PathOrToken || "").replace(/\/+$/, "") === (collection.href || "").replace(/\/+$/, ""));
if (share) {
if (share.Owner !== user) {
share_info.classList.remove("hidden");
get_element(node, "[data-name=shared-by-owner]").textContent = share.Owner;
} else {
transformed_from.classList.remove("hidden");
}
let share_option = get_element(node, "[data-name=shareoption]");
if (share_option) {
share_option.classList.add("hidden");
share_option.removeAttribute("data-name");
}
delete_btn.classList.add("hidden");
if (!/w/i.test(share.Permissions || "")) {
edit_btn.classList.add("hidden");
} else {
edit_btn.classList.remove("hidden");
}
}
title_form.textContent = collection.displayname || collection.href;
if (title_form.textContent.length > 30) {
title_form.classList.add("smalltext");
}
description_form.textContent = collection.description;
if (description_form.textContent.length > 150) {
description_form.classList.add("smalltext");
}
if (collection.type != CollectionType.WEBCAL) {
let contentcount_form_txt = (collection.contentcount > 0 ? Number(collection.contentcount).toLocaleString() : "No") + " item" + (collection.contentcount == 1 ? "" : "s") + " in collection";
if (collection.contentcount > 0) {
contentcount_form_txt += " (" + bytesToHumanReadable(collection.size) + ")";
}
contentcount_form.textContent = contentcount_form_txt;
}
let href = SERVER + collection.href;
url_form.value = href;
download_btn.href = href;
download_btn.onclick = function (event) {
event.preventDefault();
let auth = get_auth_header(user, password);
let headers = auth ? {
'Authorization': auth
} : undefined;
fetch(href, { headers: headers }).then(function (response) {
if (response.ok) {
return response.blob();
}
throw new Error("Download failed: " + response.statusText);
}).then(function (blob) {
let url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = (collection.displayname || collection.href).replace(/\/+$/, "") + (collection.type === CollectionType.ADDRESSBOOK ? ".vcf" : ".ics");
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
})["catch"](function (error) {
errorHandler.setError(error.message);
});
};
if (collection.type == CollectionType.WEBCAL) {
if (download_btn.parentElement) {
download_btn.parentElement.classList.add("hidden");
}
}
delete_btn.onclick = function () { return ondelete(collection); };
edit_btn.onclick = function () { return onedit(collection); };
share_btn.onclick = function () { return onshare(collection); };
node.classList.remove("hidden");
nodes.push(node);
if (template.parentNode) {
template.parentNode.insertBefore(node, template);
}
});
}
this.errorwrapper = function (/** @type {string} */ error) {
errorHandler.setError(error);
if (onerror) onerror(error);
};
this.show = function () {
html_scene.classList.remove("hidden");
new_btn.onclick = onnew;
upload_btn.onclick = onupload;
incomingshares_btn.onclick = onincomingshares;
collectionsCache.getChildCollections(user, password, principal_collection, this.errorwrapper, show_collections);
collectionsCache.getServerFeatures(user, password, this.errorwrapper, maybe_enable_sharing_options);
};
this.hide = function () {
html_scene.classList.add("hidden");
new_btn.onclick = null;
upload_btn.onclick = null;
incomingshares_btn.onclick = null;
// remove collection
nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = [];
};
this.release = function () {
};
this.is_transient = function () { return false; };
/** @type {Array<HTMLElement>} */ this._nodes = [];
this._errorHandler = new ErrorHandler(this._error_div);
}
_onnew() {
try {
let create_collection_scene = new CreateEditCollectionScene(this._user, this._password, this._principal_collection);
push_scene(create_collection_scene);
} catch (err) {
console.error(err);
}
return false;
}
_onupload() {
try {
let upload_scene = new UploadCollectionScene(this._user, this._password, this._principal_collection);
push_scene(upload_scene);
} catch (err) {
console.error(err);
}
return false;
}
_onincomingshares() {
try {
let incoming_sharing_scene = new IncomingSharingScene(this._user, this._password);
push_scene(incoming_sharing_scene);
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {Collection} collection
*/
_onedit(collection) {
try {
let edit_collection_scene = new CreateEditCollectionScene(this._user, this._password, collection);
push_scene(edit_collection_scene);
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {Collection} collection
*/
_onshare(collection) {
try {
let share_collection_scene = new ShareCollectionScene(this._user, this._password, collection);
push_scene(share_collection_scene);
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {Collection} collection
*/
_ondelete(collection) {
try {
let delete_collection_scene = new DeleteConfirmationScene(
this._user, this._password, "Delete Collection", collection, collection.displayname || collection.href,
delete_collection, true
);
push_scene(delete_collection_scene);
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {any[]} collections
* @param {import("../api/sharing.js").Share[]} shares
* @param {boolean} clear_error
*/
_show_collections(collections, shares, clear_error) {
/** @type {HTMLElement} */ let navBar = get_element(document, "#logoutview");
let heightOfNavBar = navBar.offsetHeight + "px";
this._html_scene.style.marginTop = heightOfNavBar;
this._html_scene.style.height = "calc(100vh - " + heightOfNavBar + ")";
if (clear_error) {
this._errorHandler.clearError();
}
// Clear old nodes
this._nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
this._nodes = [];
collections.forEach((/** @type {Collection} */ collection) => {
/** @type {HTMLElement} */ let node = /** @type {HTMLElement} */(this._template.cloneNode(true));
node.classList.remove("hidden");
/** @type {HTMLElement} */ let title_form = get_element(node, "[data-name=title]");
/** @type {HTMLElement} */ let description_form = get_element(node, "[data-name=description]");
/** @type {HTMLElement} */ let contentcount_form = get_element(node, "[data-name=contentcount]");
/** @type {HTMLInputElement} */ let url_form = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=url]"));
/** @type {HTMLElement} */ let color_form = get_element(node, "[data-name=color]");
/** @type {HTMLElement} */ let delete_btn = get_element(node, "[data-name=delete]");
/** @type {HTMLElement} */ let edit_btn = get_element(node, "[data-name=edit]");
/** @type {HTMLElement} */ let share_btn = get_element(node, "[data-name=share]");
/** @type {HTMLAnchorElement} */ let download_btn = /** @type {HTMLAnchorElement} */ (get_element(node, "[data-name=download]"));
if (collection.color) {
color_form.style.background = collection.color;
}
let possible_types = [CollectionType.ADDRESSBOOK, CollectionType.WEBCAL];
[CollectionType.CALENDAR, ""].forEach(function (e) {
[CollectionType.union(e, CollectionType.JOURNAL), e].forEach(function (e) {
[CollectionType.union(e, CollectionType.TASKS), e].forEach(function (e) {
if (e) {
possible_types.push(e);
}
});
});
});
possible_types.forEach(function (e) {
if (e !== collection.type) {
get_element(node, "[data-name=" + e + "]").classList.add("hidden");
}
});
let share_info = get_element(node, "[data-name=shared-by]");
let transformed_from = get_element(node, "[data-name=transformed-from]");
let share = (shares || []).find(
s => (s.ShareType === "map") &&
(s.PathOrToken || "").replace(/\/+$/, "") === (collection.href || "").replace(/\/+$/, ""));
if (share) {
if (share.Owner !== this._user) {
share_info.classList.remove("hidden");
get_element(node, "[data-name=shared-by-owner]").textContent = share.Owner;
} else {
transformed_from.classList.remove("hidden");
}
let share_option = get_element(node, "[data-name=shareoption]");
if (share_option) {
share_option.classList.add("hidden");
share_option.removeAttribute("data-name");
}
delete_btn.classList.add("hidden");
if (!/w/i.test(share.Permissions || "")) {
edit_btn.classList.add("hidden");
} else {
edit_btn.classList.remove("hidden");
}
}
title_form.textContent = collection.displayname || collection.href;
if (title_form.textContent.length > 30) {
title_form.classList.add("smalltext");
}
description_form.textContent = collection.description;
if (description_form.textContent.length > 150) {
description_form.classList.add("smalltext");
}
if (collection.type != CollectionType.WEBCAL) {
let contentcount_form_txt = (collection.contentcount > 0 ? Number(collection.contentcount).toLocaleString() : "No") + " item" + (collection.contentcount == 1 ? "" : "s") + " in collection";
if (collection.contentcount > 0) {
contentcount_form_txt += " (" + bytesToHumanReadable(collection.size) + ")";
}
contentcount_form.textContent = contentcount_form_txt;
}
let href = SERVER + collection.href;
url_form.value = href;
download_btn.href = href;
download_btn.onclick = (event) => {
event.preventDefault();
let auth = get_auth_header(this._user, this._password);
let headers = auth ? {
'Authorization': auth
} : undefined;
fetch(href, { headers: headers }).then(function (response) {
if (response.ok) {
return response.blob();
}
throw new Error("Download failed: " + response.statusText);
}).then(function (blob) {
let url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = (collection.displayname || collection.href).replace(/\/+$/, "") + (collection.type === CollectionType.ADDRESSBOOK ? ".vcf" : ".ics");
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
})["catch"]((error) => {
this._errorHandler.setError(error.message);
});
};
if (collection.type == CollectionType.WEBCAL) {
if (download_btn.parentElement) {
download_btn.parentElement.classList.add("hidden");
}
}
delete_btn.onclick = () => { return this._ondelete(collection); };
edit_btn.onclick = () => { return this._onedit(collection); };
share_btn.onclick = () => { return this._onshare(collection); };
node.classList.remove("hidden");
this._nodes.push(node);
if (this._template.parentNode) {
this._template.parentNode.insertBefore(node, this._template);
}
});
}
_errorwrapper(/** @type {string} */ error) {
this._errorHandler.setError(error);
if (this._onerror) this._onerror(error);
}
show() {
this._html_scene.classList.remove("hidden");
this._new_btn.onclick = () => this._onnew();
this._upload_btn.onclick = () => this._onupload();
this._incomingshares_btn.onclick = () => this._onincomingshares();
collectionsCache.getChildCollections(this._user, this._password, this._principal_collection, (e) => this._errorwrapper(e), (c, s, ce) => this._show_collections(c, s, ce));
collectionsCache.getServerFeatures(this._user, this._password, (e) => this._errorwrapper(e), maybe_enable_sharing_options);
}
hide() {
this._html_scene.classList.add("hidden");
this._new_btn.onclick = null;
this._upload_btn.onclick = null;
this._incomingshares_btn.onclick = null;
// remove collection
this._nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
this._nodes = [];
}
release() {
}
is_transient() { return false; }
}

View File

@@ -41,182 +41,187 @@ export class CreateEditCollectionScene {
* Otherwise the collection will be edited.
*/
constructor(user, password, collection) {
let edit = collection.type !== CollectionType.PRINCIPAL;
let html_scene = get_element_by_id(edit ? "editcollectionscene" : "createcollectionscene");
/** @type {?HTMLElement} */ let title_form = edit ? get_element(html_scene, "[data-name=title]") : null;
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
/** @type {?HTMLInputElement} */ let href_form = edit ? null : /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=href]"));
/** @type {HTMLInputElement} */ let displayname_form = /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=displayname]"));
/** @type {HTMLInputElement} */ let description_form = /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=description]"));
/** @type {HTMLInputElement} */ let source_form = /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=source]"));
/** @type {HTMLElement} */ let source_label = get_element(html_scene, "label[for=source]");
/** @type {HTMLSelectElement} */ let type_form = /** @type {HTMLSelectElement} */(get_element(html_scene, "[data-name=type]"));
/** @type {HTMLInputElement} */ let color_form = /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=color]"));
/** @type {HTMLElement} */ let submit_btn = get_element(html_scene, "[data-name=submit]");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
this._user = user;
this._password = password;
this._collection = collection;
this._edit = collection.type !== CollectionType.PRINCIPAL;
this._html_scene = get_element_by_id(this._edit ? "editcollectionscene" : "createcollectionscene");
this._title_form = this._edit ? get_element(this._html_scene, "[data-name=title]") : null;
this._error_form = get_element(this._html_scene, "[data-name=error]");
this._href_form = this._edit ? null : /** @type {HTMLInputElement} */(get_element(this._html_scene, "[data-name=href]"));
this._displayname_form = /** @type {HTMLInputElement} */(get_element(this._html_scene, "[data-name=displayname]"));
this._description_form = /** @type {HTMLInputElement} */(get_element(this._html_scene, "[data-name=description]"));
this._source_form = /** @type {HTMLInputElement} */(get_element(this._html_scene, "[data-name=source]"));
this._source_label = get_element(this._html_scene, "label[for=source]");
this._type_form = /** @type {HTMLSelectElement} */(get_element(this._html_scene, "[data-name=type]"));
this._color_form = /** @type {HTMLInputElement} */(get_element(this._html_scene, "[data-name=color]"));
this._submit_btn = get_element(this._html_scene, "[data-name=submit]");
this._cancel_btn = get_element(this._html_scene, "[data-name=cancel]");
/** @type {?XMLHttpRequest} */ let create_edit_req = null;
/** @type {?HTMLSelectElement} */ let saved_type_form = null;
/** @type {?XMLHttpRequest} */ this._create_edit_req = null;
/** @type {?HTMLSelectElement} */ this._saved_type_form = null;
let errorHandler = new ErrorHandler(error_form);
let validator = new FormValidator(errorHandler);
this._errorHandler = new ErrorHandler(this._error_form);
this._validator = new FormValidator(this._errorHandler);
if (!edit && href_form) {
validator.addValidator(href_form, validate_href(href_form, "HREF"));
if (!this._edit && this._href_form) {
this._validator.addValidator(this._href_form, validate_href(this._href_form, "HREF"));
}
validator.addValidator(color_form, validate_color(color_form, "Color"));
this._validator.addValidator(this._color_form, validate_color(this._color_form, "Color"));
let href = edit ? collection.href : collection.href + random_uuid() + "/";
let displayname = edit ? collection.displayname : "";
let description = edit ? collection.description : "";
let source = edit ? collection.source : "";
let type = edit ? collection.type : CollectionType.CALENDAR_JOURNAL_TASKS;
let color = edit && collection.color ? collection.color : "#" + random_hex(6);
this._href = this._edit ? collection.href : collection.href + random_uuid() + "/";
this._displayname = this._edit ? collection.displayname : "";
this._description = this._edit ? collection.description : "";
this._source = this._edit ? collection.source : "";
this._type = this._edit ? collection.type : CollectionType.CALENDAR_JOURNAL_TASKS;
this._color = this._edit && collection.color ? collection.color : "#" + random_hex(6);
if (!edit && href_form) {
href_form.addEventListener("input", onCleanHREFinput);
if (!this._edit && this._href_form) {
this._href_form.addEventListener("input", onCleanHREFinput);
}
}
function remove_invalid_types() {
if (!edit) {
return;
_remove_invalid_types() {
if (!this._edit) {
return;
}
/** @type {HTMLOptionsCollection} */ let options = this._type_form.options;
let valid_type_options = CollectionType.valid_options_for_type(this._type);
for (let i = options.length - 1; i >= 0; i--) {
if (valid_type_options.indexOf(options[i].value) < 0) {
options.remove(i);
}
/** @type {HTMLOptionsCollection} */ let options = type_form.options;
// remove all options that are not supersets
let valid_type_options = CollectionType.valid_options_for_type(type);
for (let i = options.length - 1; i >= 0; i--) {
if (valid_type_options.indexOf(options[i].value) < 0) {
options.remove(i);
}
}
_read_form() {
if (!this._edit && this._href_form) {
cleanHREFinput(this._href_form);
let newhreftxtvalue = this._href_form.value.trim().toLowerCase();
this._href = this._collection.href + newhreftxtvalue + "/";
}
this._displayname = this._displayname_form.value;
this._description = this._description_form.value;
this._source = this._source_form.value;
this._type = this._type_form.value;
this._color = this._color_form.value;
return true;
}
_fill_form() {
if (!this._edit && this._href_form) {
this._href_form.value = random_uuid();
}
this._displayname_form.value = this._displayname;
this._description_form.value = this._description;
this._source_form.value = this._source;
this._type_form.value = this._type;
this._color_form.value = this._color;
this._onTypeChange(null);
this._type_form.addEventListener("change", (e) => this._onTypeChange(e));
}
_onsubmit() {
try {
if (!this._validator.validate()) {
return false;
}
this._read_form();
let sane_color = this._color.trim();
if (sane_color) {
/** @type {?RegExpExecArray} */ let match = COLOR_RE.exec(sane_color);
if (match) {
sane_color = match[1];
}
}
}
function read_form() {
if (!edit && href_form) {
cleanHREFinput(href_form);
let newhreftxtvalue = href_form.value.trim().toLowerCase();
href = collection.href + newhreftxtvalue + "/";
}
displayname = displayname_form.value;
description = description_form.value;
source = source_form.value;
type = type_form.value;
color = color_form.value;
return true;
}
function fill_form() {
if (!edit && href_form) {
href_form.value = random_uuid();
}
displayname_form.value = displayname;
description_form.value = description;
source_form.value = source;
type_form.value = type;
color_form.value = color;
onTypeChange(null);
type_form.addEventListener("change", onTypeChange);
}
function onsubmit() {
try {
if (!validator.validate()) {
return false;
let loading_scene = new LoadingScene();
push_scene(loading_scene);
let collection = new Collection(this._href, this._type, this._displayname, this._description, sane_color, 0, 0, this._source);
let callback = (/** @type {?string} */ error1) => {
if (!is_current_scene(loading_scene)) {
return;
}
read_form();
let sane_color = color.trim();
if (sane_color) {
/** @type {?RegExpExecArray} */ let match = COLOR_RE.exec(sane_color);
if (match) {
sane_color = match[1];
}
}
let loading_scene = new LoadingScene();
push_scene(loading_scene);
let collection = new Collection(href, type, displayname, description, sane_color, 0, 0, source);
let callback = function (/** @type {?string} */ error1) {
if (!is_current_scene(loading_scene)) {
return;
}
create_edit_req = null;
if (error1) {
errorHandler.setError(error1);
pop_scene();
} else {
collectionsCache.invalidate();
pop_to_parent();
}
};
if (edit) {
create_edit_req = edit_collection(user, password, collection, callback);
this._create_edit_req = null;
if (error1) {
this._errorHandler.setError(error1);
pop_scene();
} else {
create_edit_req = create_collection(user, password, collection, callback);
collectionsCache.invalidate();
pop_to_parent();
}
} catch (err) {
console.error(err);
}
return false;
}
function oncancel() {
try {
pop_scene();
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {?Event} _e
*/
function onTypeChange(_e) {
if (type_form.value == CollectionType.WEBCAL) {
source_label.classList.remove("hidden");
source_form.classList.remove("hidden");
};
if (this._edit) {
this._create_edit_req = edit_collection(this._user, this._password, collection, callback);
} else {
source_label.classList.add("hidden");
source_form.classList.add("hidden");
this._create_edit_req = create_collection(this._user, this._password, collection, callback);
}
} catch (err) {
console.error(err);
}
return false;
}
this.show = function () {
this.release();
// Clone type_form because it's impossible to hide options without removing them
saved_type_form = type_form;
type_form = /** @type {HTMLSelectElement} */ (type_form.cloneNode(true));
if (saved_type_form.parentNode) {
saved_type_form.parentNode.replaceChild(type_form, saved_type_form);
}
remove_invalid_types();
html_scene.classList.remove("hidden");
if (edit && title_form) {
title_form.textContent = collection.displayname || collection.href;
}
fill_form();
submit_btn.onclick = onsubmit;
cancel_btn.onclick = oncancel;
validator.validate();
};
this.hide = function () {
read_form();
html_scene.classList.add("hidden");
// restore type_form
if (type_form.parentNode && saved_type_form) {
type_form.parentNode.replaceChild(saved_type_form, type_form);
type_form = saved_type_form;
}
saved_type_form = null;
submit_btn.onclick = null;
cancel_btn.onclick = null;
};
this.is_transient = function () { return false; };
this.release = function () {
if (create_edit_req !== null) {
create_edit_req.abort();
create_edit_req = null;
}
};
_oncancel() {
try {
pop_scene();
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {?Event} _e
*/
_onTypeChange(_e) {
if (this._type_form.value == CollectionType.WEBCAL) {
this._source_label.classList.remove("hidden");
this._source_form.classList.remove("hidden");
} else {
this._source_label.classList.add("hidden");
this._source_form.classList.add("hidden");
}
}
show() {
this.release();
// Clone type_form because it's impossible to hide options without removing them
this._saved_type_form = this._type_form;
this._type_form = /** @type {HTMLSelectElement} */ (this._type_form.cloneNode(true));
if (this._saved_type_form.parentNode) {
this._saved_type_form.parentNode.replaceChild(this._type_form, this._saved_type_form);
}
this._remove_invalid_types();
this._html_scene.classList.remove("hidden");
if (this._edit && this._title_form) {
this._title_form.textContent = this._collection.displayname || this._collection.href;
}
this._fill_form();
this._submit_btn.onclick = () => this._onsubmit();
this._cancel_btn.onclick = () => this._oncancel();
this._validator.validate();
}
hide() {
this._read_form();
this._html_scene.classList.add("hidden");
// restore type_form
if (this._type_form.parentNode && this._saved_type_form) {
this._type_form.parentNode.replaceChild(this._saved_type_form, this._type_form);
this._type_form = this._saved_type_form;
}
this._saved_type_form = null;
this._submit_btn.onclick = null;
this._cancel_btn.onclick = null;
}
is_transient() { return false; }
release() {
if (this._create_edit_req !== null) {
this._create_edit_req.abort();
this._create_edit_req = null;
}
}
}

View File

@@ -39,332 +39,339 @@ export class CreateEditShareScene {
* @param {Share} [share] If provided, the scene will be in edit mode.
*/
constructor(user, password, collection, shareType, share) {
let self = this;
let edit = !!share;
let pathMapped = collection.href;
/** @type {HTMLElement} */ let html_scene = get_element_by_id("newshare");
/** @type {HTMLFormElement} */ let form = /** @type {HTMLFormElement} */ (get_element(html_scene, "form"));
/** @type {HTMLElement} */ let sharemapfields = get_element(html_scene, "[data-name=sharemapfields]");
/** @type {HTMLInputElement} */ let shareuser_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=shareuser]"));
/** @type {HTMLInputElement} */ let sharehref_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=sharehref]"));
/** @type {HTMLInputElement} */ let enabled_checkbox = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=enabled]"));
/** @type {HTMLInputElement} */ let hidden_checkbox = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=hidden]"));
let permissions_ro_radio = /** @type {HTMLInputElement} */ (get_element_by_id("newshare_attr_permissions_ro"));
let permissions_rw_radio = /** @type {HTMLInputElement} */ (get_element_by_id("newshare_attr_permissions_rw"));
/** @type {HTMLDetailsElement} */ let conversions_details = /** @type {HTMLDetailsElement} */ (get_element(html_scene, "[data-name=conversions]"));
/** @type {HTMLElement} */ let conversions_container = get_element(html_scene, "[data-name=conversions_container]");
this._user = user;
this._password = password;
this._collection = collection;
this._shareType = shareType;
this._share = share;
this._edit = !!share;
this._pathMapped = collection.href;
/** @type {HTMLDetailsElement} */ let properties_fieldset = /** @type {HTMLDetailsElement} */ (get_element(html_scene, "[data-name=properties_override]"));
/** @type {HTMLInputElement} */ let displayname_override_enabled = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=displayname_override_enabled]"));
/** @type {HTMLInputElement} */ let displayname_override_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=displayname_override]"));
/** @type {HTMLInputElement} */ let description_override_enabled = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=description_override_enabled]"));
/** @type {HTMLInputElement} */ let description_override_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=description_override]"));
/** @type {HTMLInputElement} */ let color_override_enabled = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=color_override_enabled]"));
/** @type {HTMLInputElement} */ let color_override_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=color_override]"));
this._html_scene = get_element_by_id("newshare");
this._form = /** @type {HTMLFormElement} */ (get_element(this._html_scene, "form"));
this._sharemapfields = get_element(this._html_scene, "[data-name=sharemapfields]");
this._shareuser_input = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=shareuser]"));
this._sharehref_input = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=sharehref]"));
this._enabled_checkbox = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=enabled]"));
this._hidden_checkbox = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=hidden]"));
this._permissions_ro_radio = /** @type {HTMLInputElement} */ (get_element_by_id("newshare_attr_permissions_ro"));
this._permissions_rw_radio = /** @type {HTMLInputElement} */ (get_element_by_id("newshare_attr_permissions_rw"));
this._conversions_details = /** @type {HTMLDetailsElement} */ (get_element(this._html_scene, "[data-name=conversions]"));
this._conversions_container = get_element(this._html_scene, "[data-name=conversions_container]");
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let submit_btn = get_element(html_scene, "[data-name=submit]");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
this._properties_fieldset = /** @type {HTMLDetailsElement} */ (get_element(this._html_scene, "[data-name=properties_override]"));
this._displayname_override_enabled = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=displayname_override_enabled]"));
this._displayname_override_input = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=displayname_override]"));
this._description_override_enabled = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=description_override_enabled]"));
this._description_override_input = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=description_override]"));
this._color_override_enabled = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=color_override_enabled]"));
this._color_override_input = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=color_override]"));
let errorHandler = new ErrorHandler(error_form);
let map_validator = new FormValidator(errorHandler);
this._error_form = get_element(this._html_scene, "[data-name=error]");
this._submit_btn = get_element(this._html_scene, "[data-name=submit]");
this._cancel_btn = get_element(this._html_scene, "[data-name=cancel]");
map_validator.addValidator(shareuser_input, function () {
let conversion = get_selected_conversion();
this._errorHandler = new ErrorHandler(this._error_form);
this._map_validator = new FormValidator(this._errorHandler);
this._map_validator.addValidator(this._shareuser_input, () => {
let conversion = this._get_selected_conversion();
if (conversion === "bday") {
return validate_non_empty(shareuser_input, "Share User")();
return validate_non_empty(this._shareuser_input, "Share User")();
} else {
return validate_not_empty_or_equals(shareuser_input, user, "Share User")();
return validate_not_empty_or_equals(this._shareuser_input, user, "Share User")();
}
});
map_validator.addValidator(sharehref_input, validate_href(sharehref_input, "Share Href"));
this._map_validator.addValidator(this._sharehref_input, validate_href(this._sharehref_input, "Share Href"));
sharehref_input.addEventListener("input", onCleanHREFinput);
this._sharehref_input.addEventListener("input", onCleanHREFinput);
displayname_override_enabled.onchange = function () {
displayname_override_input.disabled = !displayname_override_enabled.checked;
this._displayname_override_enabled.onchange = () => {
this._displayname_override_input.disabled = !this._displayname_override_enabled.checked;
};
description_override_enabled.onchange = function () {
description_override_input.disabled = !description_override_enabled.checked;
this._description_override_enabled.onchange = () => {
this._description_override_input.disabled = !this._description_override_enabled.checked;
};
color_override_enabled.onchange = function () {
color_override_input.disabled = !color_override_enabled.checked;
this._color_override_enabled.onchange = () => {
this._color_override_input.disabled = !this._color_override_enabled.checked;
};
function get_selected_conversion() {
/** @type {HTMLInputElement | null} */
let checked = conversions_container.querySelector("input[name=conversion]:checked");
return checked ? checked.value : "none";
}
_get_selected_conversion() {
/** @type {HTMLInputElement | null} */
let checked = this._conversions_container.querySelector("input[name=conversion]:checked");
return checked ? checked.value : "none";
}
_on_conversion_change() {
let conversion = this._get_selected_conversion();
if (conversion === "bday") {
this._permissions_ro_radio.checked = true;
this._permissions_rw_radio.checked = false;
this._permissions_ro_radio.disabled = true;
this._permissions_rw_radio.disabled = true;
this._enabled_checkbox.checked = true;
this._hidden_checkbox.checked = false;
} else {
this._permissions_ro_radio.disabled = false;
this._permissions_rw_radio.disabled = false;
}
if (this._shareType === "map") {
this._map_validator.validate();
}
}
function on_conversion_change() {
let conversion = get_selected_conversion();
if (conversion === "bday") {
permissions_ro_radio.checked = true;
permissions_rw_radio.checked = false;
permissions_ro_radio.disabled = true;
permissions_rw_radio.disabled = true;
enabled_checkbox.checked = true;
hidden_checkbox.checked = false;
_oncancel() {
try {
pop_scene();
} catch (err) {
console.error(err);
}
return false;
}
_onsubmit() {
try {
if (this._shareType === "map") {
if (!this._map_validator.validate()) {
return false;
}
}
let conversion = this._get_selected_conversion();
let is_birthday = conversion === "bday";
let enabled_by_owner = is_birthday ? true : this._enabled_checkbox.checked;
let hidden_by_owner = is_birthday ? false : this._hidden_checkbox.checked;
let permissions = is_birthday ? "r" : (this._permissions_rw_radio.checked ? "rw" : "r");
/** @type {string} */ let conversion_value = conversion;
/** @type {Object<string, string>} */ let properties = {};
if (this._displayname_override_enabled.checked) {
let key = get_property_key(this._collection.type, "DISPLAYNAME");
if (key) properties[key] = this._displayname_override_input.value;
}
if (this._description_override_enabled.checked) {
let key = get_property_key(this._collection.type, "DESCRIPTION");
if (key) properties[key] = this._description_override_input.value;
}
if (this._color_override_enabled.checked) {
let key = get_property_key(this._collection.type, "COLOR");
if (key) properties[key] = this._color_override_input.value + (this._color_override_input.value ? "ff" : "");
}
let callback = (/** @type {?string} */ error) => {
if (!is_current_scene(this)) {
return;
}
if (error) {
this._errorHandler.setError(error);
} else {
// On any share-to-self (currently only bday conversion), invalidate the
// collections cache so the virtual calendar appears immediately.
if (this._shareuser_input.value === this._user) {
collectionsCache.invalidate();
}
pop_scene();
}
};
let new_share = new Share({
ShareType: this._shareType,
PathMapped: this._pathMapped,
Permissions: permissions,
EnabledByOwner: enabled_by_owner,
EnabledByUser: (this._edit && this._share) ? this._share.EnabledByUser : null,
HiddenByOwner: hidden_by_owner,
HiddenByUser: (this._edit && this._share) ? this._share.HiddenByUser : null,
Properties: properties,
User: (this._edit && this._share) ? this._share.User : this._shareuser_input.value,
PathOrToken: (this._edit && this._share) ? this._share.PathOrToken : (this._shareType === "map" ? "/" + this._shareuser_input.value + "/" + this._sharehref_input.value + "/" : ""),
Conversion: conversion_value,
});
if (this._edit) {
if (this._shareType === "map") {
update_share_by_map(this._user, this._password, new_share, callback);
} else {
update_share_by_token(this._user, this._password, new_share, callback);
}
} else {
permissions_ro_radio.disabled = false;
permissions_rw_radio.disabled = false;
}
if (shareType === "map") {
map_validator.validate();
if (this._shareType === "map") {
add_share_by_map(this._user, this._password, new_share, callback);
} else {
add_share_by_token(this._user, this._password, new_share, callback);
}
}
} catch (err) {
console.error(err);
}
return false;
}
function oncancel() {
try {
pop_scene();
} catch (err) {
console.error(err);
show() {
this.release();
this._html_scene.classList.remove("hidden");
this._html_scene.querySelectorAll("details").forEach(function (details) {
if (details.dataset.name !== "properties_override") {
details.open = true;
} else {
details.open = false;
}
return false;
}
});
this._cancel_btn.onclick = () => this._oncancel();
this._form.onsubmit = () => this._onsubmit();
function onsubmit() {
try {
if (shareType === "map") {
if (!map_validator.validate()) {
return false;
}
}
let conversion = get_selected_conversion();
let is_birthday = conversion === "bday";
let enabled_by_owner = is_birthday ? true : enabled_checkbox.checked;
let hidden_by_owner = is_birthday ? false : hidden_checkbox.checked;
let permissions = is_birthday ? "r" : (permissions_rw_radio.checked ? "rw" : "r");
/** @type {string} */ let conversion_value = conversion;
/** @type {HTMLHeadingElement} */ let title = /** @type {HTMLHeadingElement} */ (get_element(this._html_scene, "h1"));
title.textContent = this._edit ? "Edit Share" : "New Share";
this._submit_btn.textContent = this._edit ? "Save" : "Create";
/** @type {Object<string, string>} */ let properties = {};
if (displayname_override_enabled.checked) {
let key = get_property_key(collection.type, "DISPLAYNAME");
if (key) properties[key] = displayname_override_input.value;
}
if (description_override_enabled.checked) {
let key = get_property_key(collection.type, "DESCRIPTION");
if (key) properties[key] = description_override_input.value;
}
if (color_override_enabled.checked) {
let key = get_property_key(collection.type, "COLOR");
if (key) properties[key] = color_override_input.value + (color_override_input.value ? "ff" : "");
}
this._shareuser_input.value = (this._edit && this._share) ? this._share.User : "";
this._shareuser_input.disabled = this._edit;
this._enabled_checkbox.checked = (this._edit && this._share && this._share.EnabledByOwner !== null) ? this._share.EnabledByOwner : true;
this._hidden_checkbox.checked = (this._edit && this._share && this._share.HiddenByOwner !== null) ? this._share.HiddenByOwner : false;
let callback = function (/** @type {?string} */ error) {
if (!is_current_scene(self)) {
return;
}
if (error) {
errorHandler.setError(error);
let initial_conversion = (this._edit && this._share && this._share.Conversion) ? this._share.Conversion : "none";
collectionsCache.getServerFeatures(this._user, this._password, this._errorHandler.setError, (features) => {
this._conversions_container.innerHTML = "";
let supported = (features.sharing && features.sharing.SupportedConversions) || [];
if (supported.length > 0) {
let options = ["none", ...supported.filter(opt => opt !== "none")];
options.forEach(opt => {
let id = "newshare_conv_" + opt;
let radio = document.createElement("input");
radio.type = "radio";
radio.name = "conversion";
radio.value = opt;
radio.id = id;
radio.checked = (opt === initial_conversion);
radio.onchange = () => this._on_conversion_change();
let label = document.createElement("label");
label.htmlFor = id;
if (opt === "bday") {
label.textContent = "Birthday";
} else {
// On any share-to-self (currently only bday conversion), invalidate the
// collections cache so the virtual calendar appears immediately.
if (shareuser_input.value === user) {
collectionsCache.invalidate();
}
pop_scene();
label.textContent = opt.charAt(0).toUpperCase() + opt.slice(1);
}
};
let new_share = new Share({
ShareType: shareType,
PathMapped: pathMapped,
Permissions: permissions,
EnabledByOwner: enabled_by_owner,
EnabledByUser: (edit && share) ? share.EnabledByUser : null,
HiddenByOwner: hidden_by_owner,
HiddenByUser: (edit && share) ? share.HiddenByUser : null,
Properties: properties,
User: (edit && share) ? share.User : shareuser_input.value,
PathOrToken: (edit && share) ? share.PathOrToken : (shareType === "map" ? "/" + shareuser_input.value + "/" + sharehref_input.value + "/" : ""),
Conversion: conversion_value,
this._conversions_container.appendChild(radio);
this._conversions_container.appendChild(label);
});
if (edit) {
if (shareType === "map") {
update_share_by_map(user, password, new_share, callback);
} else {
update_share_by_token(user, password, new_share, callback);
}
let is_addressbook = this._collection.type === CollectionType.ADDRESSBOOK;
if (is_addressbook) {
this._conversions_details.classList.remove("hidden");
this._conversions_details.open = true;
} else {
if (shareType === "map") {
add_share_by_map(user, password, new_share, callback);
} else {
add_share_by_token(user, password, new_share, callback);
}
this._conversions_details.classList.add("hidden");
}
} catch (err) {
console.error(err);
} else {
this._conversions_details.classList.add("hidden");
}
this._on_conversion_change();
});
let displayname = this._collection.displayname || "";
let description = this._collection.description || "";
let color = this._collection.color || "#ffffff";
let displayname_override_enabled_value = false;
let description_override_enabled_value = false;
let color_override_enabled_value = false;
if (this._edit && this._share && this._share.Properties) {
let displayname_key = get_property_key(this._collection.type, "DISPLAYNAME");
if (displayname_key && this._share.Properties[displayname_key] !== undefined) {
displayname = this._share.Properties[displayname_key];
displayname_override_enabled_value = true;
}
let description_key = get_property_key(this._collection.type, "DESCRIPTION");
if (description_key && this._share.Properties[description_key] !== undefined) {
description = this._share.Properties[description_key];
description_override_enabled_value = true;
}
let color_key = get_property_key(this._collection.type, "COLOR");
if (color_key && this._share.Properties[color_key] !== undefined) {
color = this._share.Properties[color_key];
if (color.length === 9 && color.endsWith("ff")) {
color = color.substring(0, 7);
}
color_override_enabled_value = true;
}
return false;
}
this.show = function () {
this.release();
html_scene.classList.remove("hidden");
html_scene.querySelectorAll("details").forEach(function (details) {
if (details.dataset.name !== "properties_override") {
details.open = true;
} else {
details.open = false;
}
});
cancel_btn.onclick = oncancel;
form.onsubmit = onsubmit;
this._displayname_override_enabled.checked = displayname_override_enabled_value;
this._displayname_override_input.value = displayname;
this._displayname_override_input.disabled = !displayname_override_enabled_value;
/** @type {HTMLHeadingElement} */ let title = /** @type {HTMLHeadingElement} */ (get_element(html_scene, "h1"));
title.textContent = edit ? "Edit Share" : "New Share";
submit_btn.textContent = edit ? "Save" : "Create";
this._description_override_enabled.checked = description_override_enabled_value;
this._description_override_input.value = description;
this._description_override_input.disabled = !description_override_enabled_value;
shareuser_input.value = (edit && share) ? share.User : "";
shareuser_input.disabled = edit;
enabled_checkbox.checked = (edit && share && share.EnabledByOwner !== null) ? share.EnabledByOwner : true;
hidden_checkbox.checked = (edit && share && share.HiddenByOwner !== null) ? share.HiddenByOwner : false;
this._color_override_enabled.checked = color_override_enabled_value;
this._color_override_input.value = color;
this._color_override_input.disabled = !color_override_enabled_value;
let initial_conversion = (edit && share && share.Conversion) ? share.Conversion : "none";
this._properties_fieldset.classList.remove("hidden");
if (displayname_override_enabled_value || description_override_enabled_value || color_override_enabled_value) {
this._properties_fieldset.open = true;
}
collectionsCache.getServerFeatures(user, password, errorHandler.setError, (features) => {
conversions_container.innerHTML = "";
let supported = (features.sharing && features.sharing.SupportedConversions) || [];
if (supported.length > 0) {
let options = ["none", ...supported.filter(opt => opt !== "none")];
options.forEach(opt => {
let id = "newshare_conv_" + opt;
let radio = document.createElement("input");
radio.type = "radio";
radio.name = "conversion";
radio.value = opt;
radio.id = id;
radio.checked = (opt === initial_conversion);
radio.onchange = on_conversion_change;
let label = document.createElement("label");
label.htmlFor = id;
if (opt === "bday") {
label.textContent = "Birthday";
} else {
label.textContent = opt.charAt(0).toUpperCase() + opt.slice(1);
}
conversions_container.appendChild(radio);
conversions_container.appendChild(label);
});
let is_addressbook = collection.type === CollectionType.ADDRESSBOOK;
if (is_addressbook) {
conversions_details.classList.remove("hidden");
conversions_details.open = true;
} else {
conversions_details.classList.add("hidden");
}
} else {
conversions_details.classList.add("hidden");
}
on_conversion_change();
});
let displayname = collection.displayname || "";
let description = collection.description || "";
let color = collection.color || "#ffffff";
let displayname_override_enabled_value = false;
let description_override_enabled_value = false;
let color_override_enabled_value = false;
if (edit && share && share.Properties) {
let displayname_key = get_property_key(collection.type, "DISPLAYNAME");
if (displayname_key && share.Properties[displayname_key] !== undefined) {
displayname = share.Properties[displayname_key];
displayname_override_enabled_value = true;
}
let description_key = get_property_key(collection.type, "DESCRIPTION");
if (description_key && share.Properties[description_key] !== undefined) {
description = share.Properties[description_key];
description_override_enabled_value = true;
}
let color_key = get_property_key(collection.type, "COLOR");
if (color_key && share.Properties[color_key] !== undefined) {
color = share.Properties[color_key];
if (color.length === 9 && color.endsWith("ff")) {
color = color.substring(0, 7);
}
color_override_enabled_value = true;
}
let displayname_key = get_property_key(this._collection.type, "DISPLAYNAME");
if (!displayname_key) {
if (this._displayname_override_enabled.parentElement) {
this._displayname_override_enabled.parentElement.classList.add("hidden");
}
displayname_override_enabled.checked = displayname_override_enabled_value;
displayname_override_input.value = displayname;
displayname_override_input.disabled = !displayname_override_enabled_value;
description_override_enabled.checked = description_override_enabled_value;
description_override_input.value = description;
description_override_input.disabled = !description_override_enabled_value;
color_override_enabled.checked = color_override_enabled_value;
color_override_input.value = color;
color_override_input.disabled = !color_override_enabled_value;
properties_fieldset.classList.remove("hidden");
if (displayname_override_enabled_value || description_override_enabled_value || color_override_enabled_value) {
properties_fieldset.open = true;
} else {
if (this._displayname_override_enabled.parentElement) {
this._displayname_override_enabled.parentElement.classList.remove("hidden");
}
}
let description_key = get_property_key(this._collection.type, "DESCRIPTION");
if (!description_key) {
if (this._description_override_enabled.parentElement) {
this._description_override_enabled.parentElement.classList.add("hidden");
}
} else {
if (this._description_override_enabled.parentElement) {
this._description_override_enabled.parentElement.classList.remove("hidden");
}
}
let displayname_key = get_property_key(collection.type, "DISPLAYNAME");
if (!displayname_key) {
if (displayname_override_enabled.parentElement) {
displayname_override_enabled.parentElement.classList.add("hidden");
}
let color_key = get_property_key(this._collection.type, "COLOR");
if (!color_key) {
if (this._color_override_enabled.parentElement) {
this._color_override_enabled.parentElement.classList.add("hidden");
}
} else {
if (this._color_override_enabled.parentElement) {
this._color_override_enabled.parentElement.classList.remove("hidden");
}
}
if (this._shareType === "map") {
if (this._edit && this._share) {
this._sharehref_input.value = this._share.PathOrToken.split("/").filter(Boolean).pop() || "";
} else {
if (displayname_override_enabled.parentElement) {
displayname_override_enabled.parentElement.classList.remove("hidden");
}
this._sharehref_input.value = random_uuid();
}
let description_key = get_property_key(collection.type, "DESCRIPTION");
if (!description_key) {
if (description_override_enabled.parentElement) {
description_override_enabled.parentElement.classList.add("hidden");
}
} else {
if (description_override_enabled.parentElement) {
description_override_enabled.parentElement.classList.remove("hidden");
}
}
let color_key = get_property_key(collection.type, "COLOR");
if (!color_key) {
if (color_override_enabled.parentElement) {
color_override_enabled.parentElement.classList.add("hidden");
}
} else {
if (color_override_enabled.parentElement) {
color_override_enabled.parentElement.classList.remove("hidden");
}
}
if (shareType === "map") {
if (edit && share) {
sharehref_input.value = share.PathOrToken.split("/").filter(Boolean).pop() || "";
} else {
sharehref_input.value = random_uuid();
}
sharehref_input.disabled = edit;
sharemapfields.classList.remove("hidden");
map_validator.validate();
} else {
sharehref_input.value = "";
sharemapfields.classList.add("hidden");
errorHandler.clearError();
}
};
this.hide = function () {
html_scene.classList.add("hidden");
cancel_btn.onclick = null;
form.onsubmit = null;
};
this.release = function () {
};
this.is_transient = function () { return false; };
this._sharehref_input.disabled = this._edit;
this._sharemapfields.classList.remove("hidden");
this._map_validator.validate();
} else {
this._sharehref_input.value = "";
this._sharemapfields.classList.add("hidden");
this._errorHandler.clearError();
}
}
hide() {
this._html_scene.classList.add("hidden");
this._cancel_btn.onclick = null;
this._form.onsubmit = null;
}
release() {
}
is_transient() { return false; }
}

View File

@@ -42,104 +42,115 @@ export class DeleteConfirmationScene {
* @param {function} [on_success]
*/
constructor(user, password, header_title, item, item_title, delete_action, needsconfirmation, on_success) {
/** @type {HTMLElement} */ let html_scene = get_element_by_id("deleteconfirmationscene");
/** @type {HTMLElement} */ let header_html = get_element(html_scene, "[data-name=headertitle]");
this._user = user;
this._password = password;
this._item = item;
this._item_title = item_title;
this._delete_action = delete_action;
this._needsconfirmation = needsconfirmation;
this._on_success = on_success;
this._html_scene = get_element_by_id("deleteconfirmationscene");
/** @type {HTMLElement} */ let header_html = get_element(this._html_scene, "[data-name=headertitle]");
if (header_html) header_html.textContent = header_title;
/** @type {HTMLElement} */ let title_form = get_element(html_scene, "[data-name=title]");
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let confirmation_prompt = get_element(html_scene, "[data-name=confirmationprompt]");
/** @type {HTMLInputElement} */ let confirmation_txt = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=confirmationtxt]"));
/** @type {HTMLElement} */ let delete_confirmation_lbl = get_element(html_scene, "[data-name=deleteconfirmationtext]");
/** @type {HTMLElement} */ let delete_btn = get_element(html_scene, "[data-name=delete]");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
this._title_form = get_element(this._html_scene, "[data-name=title]");
this._error_form = get_element(this._html_scene, "[data-name=error]");
this._confirmation_prompt = get_element(this._html_scene, "[data-name=confirmationprompt]");
this._confirmation_txt = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=confirmationtxt]"));
this._delete_confirmation_lbl = get_element(this._html_scene, "[data-name=deleteconfirmationtext]");
this._delete_btn = get_element(this._html_scene, "[data-name=delete]");
this._cancel_btn = get_element(this._html_scene, "[data-name=cancel]");
if (needsconfirmation) {
delete_confirmation_lbl.innerHTML = DELETE_CONFIRMATION_TEXT;
confirmation_txt.value = "";
confirmation_txt.addEventListener("keydown", onkeydown);
confirmation_prompt.classList.remove("hidden");
confirmation_txt.classList.remove("hidden");
this._delete_confirmation_lbl.innerHTML = DELETE_CONFIRMATION_TEXT;
this._confirmation_txt.value = "";
this._confirmation_txt.addEventListener("keydown", (e) => this._onkeydown(e));
this._confirmation_prompt.classList.remove("hidden");
this._confirmation_txt.classList.remove("hidden");
} else {
confirmation_prompt.classList.add("hidden");
confirmation_txt.classList.add("hidden");
confirmation_txt.removeEventListener("keydown", onkeydown);
this._confirmation_prompt.classList.add("hidden");
this._confirmation_txt.classList.add("hidden");
}
/** @type {?XMLHttpRequest} */ let delete_req = null;
/** @type {?XMLHttpRequest} */ this._delete_req = null;
let errorHandler = new ErrorHandler(error_form);
let validator = new FormValidator(errorHandler);
this._errorHandler = new ErrorHandler(this._error_form);
this._validator = new FormValidator(this._errorHandler);
if (needsconfirmation) {
validator.addValidator(confirmation_txt, validate_equals(confirmation_txt, DELETE_CONFIRMATION_TEXT, "confirmation"));
this._validator.addValidator(this._confirmation_txt, validate_equals(this._confirmation_txt, DELETE_CONFIRMATION_TEXT, "confirmation"));
}
}
function ondelete() {
if (needsconfirmation && !validator.validate()) {
return false;
}
try {
let loading_scene = new LoadingScene();
push_scene(loading_scene);
delete_req = delete_action(user, password, item, function (/** @type {?string} */ error1) {
if (!is_current_scene(loading_scene)) {
return;
}
delete_req = null;
if (error1) {
pop_scene();
errorHandler.setError(error1);
_ondelete() {
if (this._needsconfirmation && !this._validator.validate()) {
return false;
}
try {
let loading_scene = new LoadingScene();
push_scene(loading_scene);
this._delete_req = this._delete_action(this._user, this._password, this._item, (/** @type {?string} */ error1) => {
if (!is_current_scene(loading_scene)) {
return;
}
this._delete_req = null;
if (error1) {
pop_scene();
this._errorHandler.setError(error1);
} else {
pop_scene();
if (this._on_success) {
this._on_success();
} else {
collectionsCache.invalidate();
pop_scene();
if (on_success) {
on_success();
} else {
collectionsCache.invalidate();
pop_scene();
}
}
});
} catch (err) {
console.error(err);
}
return false;
}
});
} catch (err) {
console.error(err);
}
return false;
}
function oncancel() {
try {
pop_scene();
} catch (err) {
console.error(err);
}
return false;
_oncancel() {
try {
pop_scene();
} catch (err) {
console.error(err);
}
return false;
}
function onkeydown(/** @type {KeyboardEvent}*/ event) {
if (event.code !== "Enter") {
return;
}
ondelete();
/** @param {KeyboardEvent} event */
_onkeydown(event) {
if (event.code !== "Enter") {
return;
}
this._ondelete();
}
this.show = function () {
this.release();
html_scene.classList.remove("hidden");
title_form.textContent = item_title;
delete_btn.onclick = ondelete;
cancel_btn.onclick = oncancel;
validator.validate();
};
this.hide = function () {
html_scene.classList.add("hidden");
cancel_btn.onclick = null;
delete_btn.onclick = null;
};
this.is_transient = function () { return false; };
this.release = function () {
if (delete_req !== null) {
delete_req.abort();
delete_req = null;
}
};
show() {
this.release();
this._html_scene.classList.remove("hidden");
this._title_form.textContent = this._item_title;
this._delete_btn.onclick = () => this._ondelete();
this._cancel_btn.onclick = () => this._oncancel();
this._validator.validate();
}
hide() {
this._html_scene.classList.add("hidden");
this._cancel_btn.onclick = null;
this._delete_btn.onclick = null;
}
is_transient() { return false; }
release() {
if (this._delete_req !== null) {
this._delete_req.abort();
this._delete_req = null;
}
}
}

View File

@@ -35,137 +35,136 @@ export class IncomingSharingScene {
* @param {?string} password
*/
constructor(user, password) {
/** @type {HTMLElement} */ let html_scene = get_element_by_id("incomingsharingscene");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
/** @type {HTMLElement} */ let error_element = get_element(html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let tbody = get_element(html_scene, "tbody[data-name=incomingsharesbody]");
/** @type {HTMLElement} */ let template = get_element(tbody, "[data-name=incomingsharerowtemplate]");
/** @type {HTMLElement} */ let table = get_element(html_scene, "table");
/** @type {HTMLElement} */ let noshares_message = get_element(html_scene, "[data-name=nosharesmessage]");
this._user = user;
this._password = password;
let error_handler = new ErrorHandler(error_element);
this._html_scene = get_element_by_id("incomingsharingscene");
this._cancel_btn = get_element(this._html_scene, "[data-name=cancel]");
this._error_element = get_element(this._html_scene, "[data-name=error]");
this._tbody = get_element(this._html_scene, "tbody[data-name=incomingsharesbody]");
this._template = get_element(this._tbody, "[data-name=incomingsharerowtemplate]");
this._table = get_element(this._html_scene, "table");
this._noshares_message = get_element(this._html_scene, "[data-name=nosharesmessage]");
/** @type {Array<HTMLElement>} */ let nodes = [];
this._error_handler = new ErrorHandler(this._error_element);
function on_cancel() {
pop_scene();
}
/** @type {Array<HTMLElement>} */ this._nodes = [];
}
/**
* @param {import("../api/sharing.js").Share} share
* @param {HTMLElement} node
*/
function toggle_share(share, node) {
let enabled_cb = /** @type {HTMLInputElement} */ (node.querySelector("[data-name=enabled]"));
let shown_cb = /** @type {HTMLInputElement} */ (node.querySelector("[data-name=shown]"));
/**
* @param {import("../api/sharing.js").Share} share
* @param {HTMLElement} node
*/
_toggle_share(share, node) {
let enabled_cb = /** @type {HTMLInputElement} */ (node.querySelector("[data-name=enabled]"));
let shown_cb = /** @type {HTMLInputElement} */ (node.querySelector("[data-name=shown]"));
// disable checkboxes while updating
enabled_cb.disabled = true;
shown_cb.disabled = true;
enabled_cb.disabled = true;
shown_cb.disabled = true;
let old_enabled = share.EnabledByUser || false;
let old_hidden = share.HiddenByUser || false;
let old_enabled = share.EnabledByUser || false;
let old_hidden = share.HiddenByUser || false;
share.EnabledByUser = enabled_cb.checked;
share.HiddenByUser = !shown_cb.checked;
error_handler.clearError();
share.EnabledByUser = enabled_cb.checked;
share.HiddenByUser = !shown_cb.checked;
this._error_handler.clearError();
update_incoming_share(user, password, share, function (error) {
enabled_cb.disabled = false;
shown_cb.disabled = !share.EnabledByUser;
update_incoming_share(this._user, this._password, share, (error) => {
enabled_cb.disabled = false;
shown_cb.disabled = !share.EnabledByUser;
if (error) {
error_handler.setError(error);
enabled_cb.checked = old_enabled;
shown_cb.checked = !old_hidden;
shown_cb.disabled = !old_enabled;
} else {
collectionsCache.invalidate();
}
});
}
/**
* @param {import("../api/sharing.js").Share[]} shares
*/
function render_shares(shares) {
// clear old nodes
nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = [];
let prefix = "/" + user + "/";
let filtered_shares = shares.filter(
share => (share.ShareType === "map")
&& share.PathOrToken.startsWith(prefix));
if (filtered_shares.length === 0) {
table.classList.add("hidden");
noshares_message.classList.remove("hidden");
if (error) {
this._error_handler.setError(error);
enabled_cb.checked = old_enabled;
shown_cb.checked = !old_hidden;
shown_cb.disabled = !old_enabled;
} else {
table.classList.remove("hidden");
noshares_message.classList.add("hidden");
collectionsCache.invalidate();
}
});
}
/**
* @param {import("../api/sharing.js").Share[]} shares
*/
_render_shares(shares) {
// clear old nodes
this._nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
this._nodes = [];
let prefix = "/" + this._user + "/";
let filtered_shares = shares.filter(
share => (share.ShareType === "map")
&& share.PathOrToken.startsWith(prefix));
if (filtered_shares.length === 0) {
this._table.classList.add("hidden");
this._noshares_message.classList.remove("hidden");
} else {
this._table.classList.remove("hidden");
this._noshares_message.classList.add("hidden");
}
filtered_shares.forEach((share) => {
let node = /** @type {HTMLElement} */(this._template.cloneNode(true));
node.classList.remove("hidden");
let pathortoken = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=pathortoken]"));
let owner_td = get_element(node, "[data-name=owner]");
let permissions_td = /** @type {HTMLElement} */ (get_element(node, "[data-name=permissions]"));
let enabled_cb = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=enabled]"));
let shown_cb = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=shown]"));
let displayPath = share.PathOrToken.substring(prefix.length);
if (displayPath.endsWith("/")) {
displayPath = displayPath.substring(0, displayPath.length - 1);
}
filtered_shares.forEach(function (share) {
let node = /** @type {HTMLElement} */(template.cloneNode(true));
node.classList.remove("hidden");
pathortoken.value = displayPath;
owner_td.textContent = share.Owner;
displayPermissionsOrConversion(share.Conversion, share.Permissions, permissions_td);
let pathortoken = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=pathortoken]"));
let owner_td = get_element(node, "[data-name=owner]");
let permissions_td = /** @type {HTMLElement} */ (get_element(node, "[data-name=permissions]"));
let enabled_cb = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=enabled]"));
let shown_cb = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=shown]"));
let enabled = share.EnabledByUser !== null ? share.EnabledByUser : true;
let shown = share.HiddenByUser !== null ? !share.HiddenByUser : true;
let displayPath = share.PathOrToken.substring(prefix.length);
if (displayPath.endsWith("/")) {
displayPath = displayPath.substring(0, displayPath.length - 1);
}
enabled_cb.checked = enabled;
shown_cb.checked = shown;
shown_cb.disabled = !enabled;
pathortoken.value = displayPath;
owner_td.textContent = share.Owner;
displayPermissionsOrConversion(share.Conversion, share.Permissions, permissions_td);
enabled_cb.onchange = () => { this._toggle_share(share, node); };
shown_cb.onchange = () => { this._toggle_share(share, node); };
let enabled = share.EnabledByUser !== null ? share.EnabledByUser : true;
let shown = share.HiddenByUser !== null ? !share.HiddenByUser : true;
this._nodes.push(node);
this._tbody.appendChild(node);
});
}
enabled_cb.checked = enabled;
shown_cb.checked = shown;
shown_cb.disabled = !enabled;
show() {
this._html_scene.classList.remove("hidden");
this._cancel_btn.onclick = () => pop_scene();
this._error_handler.clearError();
collectionsCache.getIncomingShares(this._user, this._password, this._error_handler.setError, (shares) => this._render_shares(shares));
}
enabled_cb.onchange = function () { toggle_share(share, node); };
shown_cb.onchange = function () { toggle_share(share, node); };
hide() {
this._html_scene.classList.add("hidden");
this._cancel_btn.onclick = null;
this._error_handler.clearError();
}
nodes.push(node);
tbody.appendChild(node);
});
}
is_transient() { return false; }
this.show = function () {
html_scene.classList.remove("hidden");
cancel_btn.onclick = on_cancel;
error_handler.clearError();
collectionsCache.getIncomingShares(user, password, error_handler.setError, render_shares);
};
this.hide = function () {
html_scene.classList.add("hidden");
cancel_btn.onclick = null;
error_handler.clearError();
};
this.is_transient = function () { return false; };
this.release = function () {
error_handler.clearError();
nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = [];
};
release() {
this._error_handler.clearError();
this._nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
this._nodes = [];
}
}

View File

@@ -31,166 +31,167 @@ import { LoadingScene } from "./LoadingScene.js";
import { Scene, is_current_scene, pop_scene, pop_to_root, push_scene, replace_scene } from "./scene_manager.js";
/**
* @constructor
* @implements {Scene}
*/
export class LoginScene {
constructor() {
/** @type {HTMLElement} */ let html_scene = get_element_by_id("loginscene");
/** @type {HTMLElement} */ let form = get_element(html_scene, "[data-name=form]");
/** @type {HTMLInputElement} */ let user_form = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=user]"));
/** @type {HTMLInputElement} */ let password_form = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=password]"));
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let logout_view = get_element_by_id("logoutview");
/** @type {HTMLElement} */ let logout_user_form = get_element(logout_view, "[data-name=user]");
/** @type {HTMLElement} */ let logout_btn = get_element(logout_view, "[data-name=logout]");
/** @type {HTMLElement} */ let refresh_btn = get_element(logout_view, "[data-name=refresh]");
this._html_scene = get_element_by_id("loginscene");
this._form = get_element(this._html_scene, "[data-name=form]");
this._user_form = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=user]"));
this._password_form = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=password]"));
this._error_form = get_element(this._html_scene, "[data-name=error]");
this._logout_view = get_element_by_id("logoutview");
this._logout_user_form = get_element(this._logout_view, "[data-name=user]");
this._logout_btn = get_element(this._logout_view, "[data-name=logout]");
this._refresh_btn = get_element(this._logout_view, "[data-name=refresh]");
let user = "";
/** @type {?XMLHttpRequest} */ let principal_req = null;
let errorHandler = new ErrorHandler(error_form);
let validator = new FormValidator(errorHandler);
validator.addValidator(user_form, validate_non_empty(user_form, "Username"));
this._user = "";
/** @type {?XMLHttpRequest} */ this._principal_req = null;
this._errorHandler = new ErrorHandler(this._error_form);
this._validator = new FormValidator(this._errorHandler);
this._validator.addValidator(this._user_form, validate_non_empty(this._user_form, "Username"));
}
function read_form() {
user = user_form.value;
_read_form() {
this._user = this._user_form.value;
}
_fill_form() {
this._user_form.value = this._user;
this._password_form.value = "";
}
/**
* @param {string} p_user
* @param {?string} p_password
*/
_perform_login(p_user, p_password) {
this._user = p_user;
this._fill_form();
// setup logout
this._logout_view.classList.remove("hidden");
if (p_password === null) {
this._logout_btn.classList.add("hidden");
} else {
this._logout_btn.classList.remove("hidden");
}
function fill_form() {
user_form.value = user;
password_form.value = "";
}
/**
* @param {string} p_user
* @param {?string} p_password
*/
function perform_login(p_user, p_password) {
user = p_user;
fill_form();
// setup logout
logout_view.classList.remove("hidden");
if (p_password === null) {
logout_btn.classList.add("hidden");
} else {
logout_btn.classList.remove("hidden");
this._logout_btn.onclick = () => this._onlogout();
this._refresh_btn.onclick = () => this._refresh();
this._logout_user_form.textContent = this._user + "'s Collections";
// Fetch principal
let loading_scene = new LoadingScene();
push_scene(loading_scene);
this._principal_req = get_principal(this._user, p_password, (/** @type {?Collection} */ principal_collection, error1) => {
if (!is_current_scene(loading_scene)) {
return;
}
logout_btn.onclick = onlogout;
refresh_btn.onclick = refresh;
logout_user_form.textContent = user + "'s Collections";
// Fetch principal
let loading_scene = new LoadingScene();
push_scene(loading_scene);
principal_req = get_principal(user, p_password, function (/** @type {?Collection} */ principal_collection, error1) {
if (!is_current_scene(loading_scene)) {
return;
}
principal_req = null;
if (error1) {
errorHandler.setError(error1);
pop_scene();
} else if (principal_collection) {
// show collections
let saved_user = user;
user = "";
let collections_scene = new CollectionsScene(
saved_user, p_password, principal_collection, function (/** @type {?string} */ error1) {
errorHandler.setError(error1);
user = saved_user;
});
replace_scene(collections_scene);
}
});
}
function onlogin() {
try {
collectionsCache.invalidate();
read_form();
let password = password_form.value;
if (!validator.validate()) {
return false;
}
perform_login(user, password);
} catch (err) {
console.error(err);
}
return false;
}
let onlogout = function () {
try {
collectionsCache.invalidate();
user = "";
pop_to_root();
} catch (err) {
console.error(err);
}
return false;
};
function remove_logout() {
logout_view.classList.add("hidden");
logout_btn.onclick = null;
refresh_btn.onclick = null;
logout_user_form.textContent = "";
}
function refresh() {
// The easiest way to refresh is to push a LoadingScene onto the stack and then pop it
// forcing the scene below it, the Collections Scene to refresh itself.
collectionsCache.invalidate();
push_scene(new LoadingScene());
pop_scene();
}
this.show = function () {
remove_logout();
fill_form();
form.onsubmit = onlogin;
html_scene.classList.remove("hidden");
user_form.focus();
// Probe for existing authentication (e.g. X-Remote-User)
// Use fetch with credentials: 'omit' to avoid browser login prompt on 401
fetch(SERVER + ROOT_PATH, {
method: 'PROPFIND',
headers: { 'Depth': '0' },
credentials: 'omit'
}).then(function (response) {
if (response.ok) {
// Authenticated! Now it's safe to call get_principal
get_principal(null, null, function (/** @type {?Collection} */ principal_collection, error) {
if (!error && principal_collection) {
let authenticated_user = principal_collection.displayname;
if (!authenticated_user) {
let href = principal_collection.href.replace(/\/+$/, "");
if (href && href !== ROOT_PATH.replace(/\/+$/, "")) {
authenticated_user = href.substring(href.lastIndexOf("/") + 1);
}
}
perform_login(authenticated_user, null);
}
this._principal_req = null;
if (error1) {
this._errorHandler.setError(error1);
pop_scene();
} else if (principal_collection) {
// show collections
let saved_user = this._user;
this._user = "";
let collections_scene = new CollectionsScene(
saved_user, p_password, principal_collection, (/** @type {?string} */ error1) => {
this._errorHandler.setError(error1);
this._user = saved_user;
});
}
})["catch"](function () {
// Ignore error: we are not authenticated or something else went wrong
});
};
this.hide = function () {
read_form();
html_scene.classList.add("hidden");
form.onsubmit = null;
};
this.is_transient = function () { return false; };
this.release = function () {
// cancel pending requests
if (principal_req !== null) {
principal_req.abort();
principal_req = null;
replace_scene(collections_scene);
}
remove_logout();
};
});
}
_onlogin() {
try {
collectionsCache.invalidate();
this._read_form();
let password = this._password_form.value;
if (!this._validator.validate()) {
return false;
}
this._perform_login(this._user, password);
} catch (err) {
console.error(err);
}
return false;
}
_onlogout() {
try {
collectionsCache.invalidate();
this._user = "";
pop_to_root();
} catch (err) {
console.error(err);
}
return false;
}
_remove_logout() {
this._logout_view.classList.add("hidden");
this._logout_btn.onclick = null;
this._refresh_btn.onclick = null;
this._logout_user_form.textContent = "";
}
_refresh() {
// The easiest way to refresh is to push a LoadingScene onto the stack and then pop it
// forcing the scene below it, the Collections Scene to refresh itself.
collectionsCache.invalidate();
push_scene(new LoadingScene());
pop_scene();
}
show() {
this._remove_logout();
this._fill_form();
this._form.onsubmit = () => this._onlogin();
this._html_scene.classList.remove("hidden");
this._user_form.focus();
// Probe for existing authentication (e.g. X-Remote-User)
// Use fetch with credentials: 'omit' to avoid browser login prompt on 401
fetch(SERVER + ROOT_PATH, {
method: 'PROPFIND',
headers: { 'Depth': '0' },
credentials: 'omit'
}).then((response) => {
if (response.ok) {
// Authenticated! Now it's safe to call get_principal
get_principal(null, null, (/** @type {?Collection} */ principal_collection, error) => {
if (!error && principal_collection) {
let authenticated_user = principal_collection.displayname;
if (!authenticated_user) {
let href = principal_collection.href.replace(/\/+$/, "");
if (href && href !== ROOT_PATH.replace(/\/+$/, "")) {
authenticated_user = href.substring(href.lastIndexOf("/") + 1);
}
}
this._perform_login(authenticated_user, null);
}
});
}
})["catch"](function () {
// Ignore error: we are not authenticated or something else went wrong
});
}
hide() {
this._read_form();
this._html_scene.classList.add("hidden");
this._form.onsubmit = null;
}
is_transient() { return false; }
release() {
// cancel pending requests
if (this._principal_req !== null) {
this._principal_req.abort();
this._principal_req = null;
}
this._remove_logout();
}
}

View File

@@ -43,91 +43,96 @@ export class ShareCollectionScene {
* @param {Collection} collection The collection on which to edit sharing setting. Must exist.
*/
constructor(user, password, collection) {
this._user = user;
this._password = password;
this._collection = collection;
let html_scene = get_element_by_id("sharecollectionscene");
this._html_scene = get_element_by_id("sharecollectionscene");
this._cancel_btn = get_element(this._html_scene, "[data-name=cancel]");
this._share_by_token_btn = get_element(this._html_scene, "button[data-name=sharebytoken]");
this._share_by_map_btn = get_element(this._html_scene, "button[data-name=sharebymap]");
this._share_by_token_div = get_element(this._html_scene, "div[data-name=sharebytoken]");
this._share_by_map_div = get_element(this._html_scene, "div[data-name=sharebymap]");
this._error_form = get_element(this._html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
/** @type {HTMLElement} */ let share_by_token_btn = get_element(html_scene, "button[data-name=sharebytoken]");
/** @type {HTMLElement} */ let share_by_map_btn = get_element(html_scene, "button[data-name=sharebymap]");
/** @type {HTMLElement} */ let share_by_token_div = get_element(html_scene, "div[data-name=sharebytoken]");
/** @type {HTMLElement} */ let share_by_map_div = get_element(html_scene, "div[data-name=sharebymap]");
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
this._errorHandler = new ErrorHandler(this._error_form);
let errorHandler = new ErrorHandler(error_form);
/** @type {HTMLElement} */ let title = get_element(html_scene, "[data-name=title]");
function oncancel() {
try {
pop_scene();
} catch (err) {
console.error(err);
}
return false;
}
function onsharebytoken() {
let create_edit_share_scene = new CreateEditShareScene(user, password, collection, "token");
push_scene(create_edit_share_scene);
}
function onsharebymap() {
let create_edit_share_scene = new CreateEditShareScene(user, password, collection, "map");
push_scene(create_edit_share_scene);
}
this.show = function () {
this.release();
html_scene.classList.remove("hidden");
html_scene.querySelectorAll("details").forEach(function (details) {
details.open = true;
});
cancel_btn.onclick = oncancel;
collectionsCache.getServerFeatures(user, password, errorHandler.setError, (features) => {
if (features.sharing && features.sharing.PermittedCreateCollectionByToken) {
if (share_by_token_btn) {
share_by_token_btn.classList.remove("hidden");
share_by_token_btn.onclick = onsharebytoken;
}
} else {
if (share_by_token_btn) share_by_token_btn.classList.add("hidden");
}
if (features.sharing && features.sharing.FeatureEnabledCollectionByToken) {
if (share_by_token_div) share_by_token_div.classList.remove("hidden");
} else {
if (share_by_token_div) share_by_token_div.classList.add("hidden");
}
if (features.sharing && features.sharing.PermittedCreateCollectionByMap) {
if (share_by_map_btn) {
share_by_map_btn.classList.remove("hidden");
share_by_map_btn.onclick = onsharebymap;
}
} else {
if (share_by_map_btn) share_by_map_btn.classList.add("hidden");
}
if (features.sharing && features.sharing.FeatureEnabledCollectionByMap) {
if (share_by_map_div) share_by_map_div.classList.remove("hidden");
} else {
if (share_by_map_div) share_by_map_div.classList.add("hidden");
}
});
title.textContent = collection.displayname || collection.href;
update_share_list(user, password, collection, errorHandler);
};
this.hide = function () {
html_scene.classList.add("hidden");
cancel_btn.onclick = null;
};
this.release = function () {
};
this.is_transient = function () { return false; };
this._title = get_element(this._html_scene, "[data-name=title]");
}
_oncancel() {
try {
pop_scene();
} catch (err) {
console.error(err);
}
return false;
}
_onsharebytoken() {
let create_edit_share_scene = new CreateEditShareScene(this._user, this._password, this._collection, "token");
push_scene(create_edit_share_scene);
}
_onsharebymap() {
let create_edit_share_scene = new CreateEditShareScene(this._user, this._password, this._collection, "map");
push_scene(create_edit_share_scene);
}
show() {
this.release();
this._html_scene.classList.remove("hidden");
this._html_scene.querySelectorAll("details").forEach(function (details) {
details.open = true;
});
this._cancel_btn.onclick = () => this._oncancel();
collectionsCache.getServerFeatures(this._user, this._password, this._errorHandler.setError, (features) => {
if (features.sharing && features.sharing.PermittedCreateCollectionByToken) {
if (this._share_by_token_btn) {
this._share_by_token_btn.classList.remove("hidden");
this._share_by_token_btn.onclick = () => this._onsharebytoken();
}
} else {
if (this._share_by_token_btn) this._share_by_token_btn.classList.add("hidden");
}
if (features.sharing && features.sharing.FeatureEnabledCollectionByToken) {
if (this._share_by_token_div) this._share_by_token_div.classList.remove("hidden");
} else {
if (this._share_by_token_div) this._share_by_token_div.classList.add("hidden");
}
if (features.sharing && features.sharing.PermittedCreateCollectionByMap) {
if (this._share_by_map_btn) {
this._share_by_map_btn.classList.remove("hidden");
this._share_by_map_btn.onclick = () => this._onsharebymap();
}
} else {
if (this._share_by_map_btn) this._share_by_map_btn.classList.add("hidden");
}
if (features.sharing && features.sharing.FeatureEnabledCollectionByMap) {
if (this._share_by_map_div) this._share_by_map_div.classList.remove("hidden");
} else {
if (this._share_by_map_div) this._share_by_map_div.classList.add("hidden");
}
});
this._title.textContent = this._collection.displayname || this._collection.href;
update_share_list(this._user, this._password, this._collection, this._errorHandler);
}
hide() {
this._html_scene.classList.add("hidden");
this._cancel_btn.onclick = null;
}
release() {
}
is_transient() { return false; }
}
/**

View File

@@ -37,202 +37,208 @@ export class UploadCollectionScene {
* @param {Collection} collection parent collection
*/
constructor(user, password, collection) {
/** @type {HTMLElement} */ let html_scene = get_element_by_id("uploadcollectionscene");
/** @type {HTMLElement} */ let template = get_element(html_scene, "[data-name=filetemplate]");
/** @type {HTMLElement} */ let upload_btn = get_element(html_scene, "[data-name=submit]");
/** @type {HTMLElement} */ let close_btn = get_element(html_scene, "[data-name=close]");
/** @type {HTMLInputElement} */ let uploadfile_form = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=uploadfile]"));
/** @type {HTMLElement} */ let uploadfile_lbl = get_element(html_scene, "label[for=uploadfile]");
/** @type {HTMLInputElement} */ let href_form = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=href]"));
/** @type {HTMLElement} */ let href_label = get_element(html_scene, "label[for=href]");
/** @type {HTMLElement} */ let hreflimitmsg_html = get_element(html_scene, "[data-name=hreflimitmsg]");
/** @type {HTMLElement} */ let pending_html = get_element(html_scene, "[data-name=pending]");
/** @type {HTMLElement} */ let error_form = get_element(html_scene, ":scope > span[data-name=error]");
this._user = user;
this._password = password;
this._collection = collection;
let files = uploadfile_form.files;
href_form.addEventListener("input", onCleanHREFinput);
upload_btn.onclick = upload_start;
uploadfile_form.onchange = onfileschange;
this._html_scene = get_element_by_id("uploadcollectionscene");
this._template = get_element(this._html_scene, "[data-name=filetemplate]");
this._upload_btn = get_element(this._html_scene, "[data-name=submit]");
this._close_btn = get_element(this._html_scene, "[data-name=close]");
this._uploadfile_form = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=uploadfile]"));
this._uploadfile_lbl = get_element(this._html_scene, "label[for=uploadfile]");
this._href_form = /** @type {HTMLInputElement} */ (get_element(this._html_scene, "[data-name=href]"));
this._href_label = get_element(this._html_scene, "label[for=href]");
this._hreflimitmsg_html = get_element(this._html_scene, "[data-name=hreflimitmsg]");
this._pending_html = get_element(this._html_scene, "[data-name=pending]");
this._error_form = get_element(this._html_scene, ":scope > span[data-name=error]");
href_form.value = "";
let href = "";
this._files = this._uploadfile_form.files;
this._href_form.addEventListener("input", onCleanHREFinput);
this._upload_btn.onclick = () => this._upload_start();
this._uploadfile_form.onchange = () => this._onfileschange();
let errorHandler = new ErrorHandler(error_form);
let validator = new FormValidator(errorHandler);
this._href_form.value = "";
this._href = "";
validator.addValidator(href_form, validate_href(href_form, "HREF"));
validator.addValidator(uploadfile_form, validate_files(uploadfile_form, "file"));
this._errorHandler = new ErrorHandler(this._error_form);
this._validator = new FormValidator(this._errorHandler);
/** @type {?XMLHttpRequest} */ let upload_req = null;
/** @type {Array<?string>} */ let results = [];
/** @type {?Array<HTMLElement>} */ let nodes = null;
this._validator.addValidator(this._href_form, validate_href(this._href_form, "HREF"));
this._validator.addValidator(this._uploadfile_form, validate_files(this._uploadfile_form, "file"));
function upload_start() {
try {
if (!validator.validate()) {
return false;
}
if (!files) {
return false;
}
read_form();
uploadfile_form.classList.add("hidden");
uploadfile_lbl.classList.add("hidden");
href_form.classList.add("hidden");
href_label.classList.add("hidden");
hreflimitmsg_html.classList.add("hidden");
upload_btn.classList.add("hidden");
close_btn.classList.add("hidden");
/** @type {?XMLHttpRequest} */ this._upload_req = null;
/** @type {Array<?string>} */ this._results = [];
/** @type {?Array<HTMLElement>} */ this._nodes = null;
}
pending_html.classList.remove("hidden");
nodes = [];
for (let i = 0; i < files.length; i++) {
let file = files[i];
let node = /** @type {HTMLElement} */ (template.cloneNode(true));
node.classList.remove("hidden");
let name_form = get_element(node, "[data-name=name]");
name_form.textContent = file.name;
node.classList.remove("hidden");
nodes.push(node);
updateFileStatus(i);
if (template.parentNode) {
template.parentNode.insertBefore(node, template);
}
}
upload_next();
} catch (err) {
console.error(err);
_upload_start() {
try {
if (!this._validator.validate()) {
return false;
}
return false;
}
if (!this._files) {
return false;
}
this._read_form();
this._uploadfile_form.classList.add("hidden");
this._uploadfile_lbl.classList.add("hidden");
this._href_form.classList.add("hidden");
this._href_label.classList.add("hidden");
this._hreflimitmsg_html.classList.add("hidden");
this._upload_btn.classList.add("hidden");
this._close_btn.classList.add("hidden");
function upload_next() {
if (!files) {
this._pending_html.classList.remove("hidden");
this._nodes = [];
for (let i = 0; i < this._files.length; i++) {
let file = this._files[i];
let node = /** @type {HTMLElement} */ (this._template.cloneNode(true));
node.classList.remove("hidden");
let name_form = get_element(node, "[data-name=name]");
name_form.textContent = file.name;
node.classList.remove("hidden");
this._nodes.push(node);
this._updateFileStatus(i);
if (this._template.parentNode) {
this._template.parentNode.insertBefore(node, this._template);
}
}
this._upload_next();
} catch (err) {
console.error(err);
}
return false;
}
_upload_next() {
if (!this._files) {
return;
}
try {
if (this._files.length === this._results.length) {
this._pending_html.classList.add("hidden");
this._close_btn.classList.remove("hidden");
return;
}
try {
if (files.length === results.length) {
pending_html.classList.add("hidden");
close_btn.classList.remove("hidden");
return;
} else {
let file = files[results.length];
if (files.length > 1 || href.length == 0) {
href = random_uuid();
}
let upload_href = collection.href + href + "/";
upload_req = upload_collection(user, password, upload_href, file, function (result) {
upload_req = null;
results.push(result);
updateFileStatus(results.length - 1);
upload_next();
});
}
} catch (err) {
console.error(err);
}
}
function onclose() {
try {
if (results.length > 0) {
collectionsCache.invalidate();
}
pop_scene();
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {number} i
*/
function updateFileStatus(i) {
if (nodes === null) {
return;
}
/** @type {HTMLElement} */ let file_success_form = get_element(nodes[i], "[data-name=success]");
/** @type {HTMLElement} */ let file_error_form = get_element(nodes[i], "[data-name=error]");
if (results.length > i) {
if (results[i]) {
file_success_form.classList.add("hidden");
file_error_form.textContent = "Error: " + results[i];
error_form.classList.remove("hidden");
} else {
file_success_form.classList.remove("hidden");
file_error_form.classList.add("hidden");
}
} else {
let file = this._files[this._results.length];
if (this._files.length > 1 || this._href.length == 0) {
this._href = random_uuid();
}
let upload_href = this._collection.href + this._href + "/";
this._upload_req = upload_collection(this._user, this._password, upload_href, file, (result) => {
this._upload_req = null;
this._results.push(result);
this._updateFileStatus(this._results.length - 1);
this._upload_next();
});
}
} catch (err) {
console.error(err);
}
}
_onclose() {
try {
if (this._results.length > 0) {
collectionsCache.invalidate();
}
pop_scene();
} catch (err) {
console.error(err);
}
return false;
}
/**
* @param {number} i
*/
_updateFileStatus(i) {
if (this._nodes === null) {
return;
}
/** @type {HTMLElement} */ let file_success_form = get_element(this._nodes[i], "[data-name=success]");
/** @type {HTMLElement} */ let file_error_form = get_element(this._nodes[i], "[data-name=error]");
if (this._results.length > i) {
if (this._results[i]) {
file_success_form.classList.add("hidden");
file_error_form.textContent = "Error: " + this._results[i];
this._error_form.classList.remove("hidden");
} else {
file_success_form.classList.remove("hidden");
file_error_form.classList.add("hidden");
}
} else {
file_success_form.classList.add("hidden");
file_error_form.classList.add("hidden");
}
}
function read_form() {
cleanHREFinput(href_form);
href = href_form.value.trim().toLowerCase();
files = uploadfile_form.files;
return true;
}
_read_form() {
cleanHREFinput(this._href_form);
this._href = this._href_form.value.trim().toLowerCase();
this._files = this._uploadfile_form.files;
return true;
}
function onfileschange() {
files = uploadfile_form.files;
if (files && files.length > 1) {
hreflimitmsg_html.classList.remove("hidden");
href_form.classList.add("hidden");
href_label.classList.add("hidden");
href_form.value = random_uuid(); // fake HREF, will be replaced on upload
_onfileschange() {
this._files = this._uploadfile_form.files;
if (this._files && this._files.length > 1) {
this._hreflimitmsg_html.classList.remove("hidden");
this._href_form.classList.add("hidden");
this._href_label.classList.add("hidden");
this._href_form.value = random_uuid(); // fake HREF, will be replaced on upload
} else {
this._hreflimitmsg_html.classList.add("hidden");
this._href_form.classList.remove("hidden");
this._href_label.classList.remove("hidden");
if (this._files && this._files.length > 0) {
this._href_form.value = this._files[0].name.replace(/\.(ics|vcf)$/, '');
} else {
hreflimitmsg_html.classList.add("hidden");
href_form.classList.remove("hidden");
href_label.classList.remove("hidden");
if (files && files.length > 0) {
href_form.value = files[0].name.replace(/\.(ics|vcf)$/, '');
} else {
href_form.value = "";
}
this._href_form.value = "";
}
return false;
}
return false;
}
this.show = function () {
html_scene.classList.remove("hidden");
close_btn.onclick = onclose;
};
show() {
this._html_scene.classList.remove("hidden");
this._close_btn.onclick = () => this._onclose();
}
this.hide = function () {
html_scene.classList.add("hidden");
close_btn.classList.remove("hidden");
upload_btn.classList.remove("hidden");
uploadfile_form.classList.remove("hidden");
uploadfile_lbl.classList.remove("hidden");
href_form.classList.remove("hidden");
href_label.classList.remove("hidden");
hreflimitmsg_html.classList.add("hidden");
pending_html.classList.add("hidden");
errorHandler.clearError();
close_btn.onclick = null;
upload_btn.onclick = null;
href_form.value = "";
uploadfile_form.value = "";
if (nodes == null) {
return;
hide() {
this._html_scene.classList.add("hidden");
this._close_btn.classList.remove("hidden");
this._upload_btn.classList.remove("hidden");
this._uploadfile_form.classList.remove("hidden");
this._uploadfile_lbl.classList.remove("hidden");
this._href_form.classList.remove("hidden");
this._href_label.classList.remove("hidden");
this._hreflimitmsg_html.classList.add("hidden");
this._pending_html.classList.add("hidden");
this._errorHandler.clearError();
this._close_btn.onclick = null;
this._upload_btn.onclick = null;
this._href_form.value = "";
this._uploadfile_form.value = "";
if (this._nodes == null) {
return;
}
this._nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
nodes.forEach(function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = null;
};
this.is_transient = function () { return false; };
this.release = function () {
if (upload_req !== null) {
upload_req.abort();
upload_req = null;
}
};
});
this._nodes = null;
}
is_transient() { return false; }
release() {
if (this._upload_req !== null) {
this._upload_req.abort();
this._upload_req = null;
}
}
}