Merge pull request #2054 from maxberger/master
UI: More reorganization of code, support browser back/forward buttons!
This commit is contained in:
@@ -126,3 +126,24 @@ def test_navigation_refresh_button(
|
||||
page.click('#logoutview a[data-name="refresh"]')
|
||||
# It shows LoadingScene briefly then back to CollectionsScene
|
||||
expect(page.locator("#collectionsscene")).to_be_visible()
|
||||
|
||||
|
||||
def test_navigation_browser_history(
|
||||
context: BrowserContext, page: Page, radicale_server: str, config: Config
|
||||
) -> None:
|
||||
login(page, radicale_server, config, context=context)
|
||||
expect(page.locator("#collectionsscene")).to_be_visible()
|
||||
|
||||
# Navigate forward inside the SPA to CreateEditCollectionScene
|
||||
page.click('a[data-name="new"]')
|
||||
expect(page.locator("#createcollectionscene")).to_be_visible()
|
||||
|
||||
# Emulate browser Back button
|
||||
page.go_back()
|
||||
expect(page.locator("#createcollectionscene")).to_be_hidden()
|
||||
expect(page.locator("#collectionsscene")).to_be_visible()
|
||||
|
||||
# Emulate browser Forward button
|
||||
page.go_forward()
|
||||
expect(page.locator("#collectionsscene")).to_be_hidden()
|
||||
expect(page.locator("#createcollectionscene")).to_be_visible()
|
||||
|
||||
@@ -191,7 +191,7 @@ main {
|
||||
#collectionsscene article p {
|
||||
font-size: 1em;
|
||||
max-height: 130px;
|
||||
overflow: overlay;
|
||||
overflow: auto;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -476,7 +476,8 @@ a.blue:active {
|
||||
}
|
||||
|
||||
button.inline {
|
||||
padding-inline: 1px;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
min-width: 2em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -127,13 +127,13 @@
|
||||
<option value="WEBCAL">webcal</option>
|
||||
</select>
|
||||
<label for="displayname">Title:</label>
|
||||
<input data-name="displayname" type="text">
|
||||
<input data-name="displayname" id="displayname" type="text">
|
||||
<label for="description">Description:</label>
|
||||
<input data-name="description" type="text">
|
||||
<input data-name="description" id="description" type="text">
|
||||
<label for="source">Source:</label>
|
||||
<input data-name="source" type="url">
|
||||
<input data-name="source" id="source" type="url">
|
||||
<label for="color">Color:</label>
|
||||
<input data-name="color" type="color">
|
||||
<input data-name="color" id="color" type="color">
|
||||
<br>
|
||||
<span class="error hidden" data-name="error"></span>
|
||||
<br>
|
||||
@@ -309,15 +309,15 @@
|
||||
<option value="WEBCAL">Webcal</option>
|
||||
</select>
|
||||
<label for="href">HREF:</label>
|
||||
<input data-name="href" type="text">
|
||||
<input data-name="href" id="href" type="text">
|
||||
<label for="displayname">Title:</label>
|
||||
<input data-name="displayname" type="text">
|
||||
<input data-name="displayname" id="create_displayname" type="text">
|
||||
<label for="description">Description:</label>
|
||||
<input data-name="description" type="text">
|
||||
<input data-name="description" id="create_description" type="text">
|
||||
<label for="source">Source:</label>
|
||||
<input data-name="source" type="url">
|
||||
<input data-name="source" id="create_source" type="url">
|
||||
<label for="color">Color:</label>
|
||||
<input data-name="color" type="color">
|
||||
<input data-name="color" id="create_color" type="color">
|
||||
<br>
|
||||
<span class="error" data-name="error"></span>
|
||||
<br>
|
||||
@@ -342,9 +342,9 @@
|
||||
</div>
|
||||
<form>
|
||||
<label for="uploadfile">File:</label>
|
||||
<input data-name="uploadfile" type="file" accept=".ics, .vcf" multiple>
|
||||
<label for="href">HREF:</label>
|
||||
<input data-name="href" type="text">
|
||||
<input data-name="uploadfile" id="uploadfile" type="file" accept=".ics, .vcf" multiple>
|
||||
<label for="upload_href">HREF:</label>
|
||||
<input data-name="href" id="upload_href" type="text">
|
||||
<small data-name="hreflimitmsg" class="hidden">You can only specify the HREF if you upload 1 file.</small>
|
||||
<button type="submit" class="green" data-name="submit">Upload</button>
|
||||
<button type="button" class="red" data-name="close">Close</button>
|
||||
|
||||
@@ -45,250 +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 () {
|
||||
};
|
||||
/** @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; }
|
||||
}
|
||||
@@ -41,181 +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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,331 +39,337 @@ 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();
|
||||
if (conversion === "bday") {
|
||||
return validate_non_empty(shareuser_input, "Share User")();
|
||||
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 != "none") {
|
||||
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;
|
||||
};
|
||||
function get_selected_conversion() {
|
||||
/** @type {HTMLInputElement | null} */
|
||||
let checked = conversions_container.querySelector("input[name=conversion]:checked");
|
||||
return checked ? checked.value : "none";
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
permissions_ro_radio.disabled = false;
|
||||
permissions_rw_radio.disabled = false;
|
||||
}
|
||||
if (shareType === "map") {
|
||||
map_validator.validate();
|
||||
}
|
||||
}
|
||||
|
||||
function oncancel() {
|
||||
try {
|
||||
pop_scene();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 {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" : "");
|
||||
}
|
||||
|
||||
let callback = function (/** @type {?string} */ error) {
|
||||
if (!is_current_scene(self)) {
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
errorHandler.setError(error);
|
||||
} 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();
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
if (edit) {
|
||||
if (shareType === "map") {
|
||||
update_share_by_map(user, password, new_share, callback);
|
||||
} else {
|
||||
update_share_by_token(user, password, new_share, callback);
|
||||
}
|
||||
} else {
|
||||
if (shareType === "map") {
|
||||
add_share_by_map(user, password, new_share, callback);
|
||||
} else {
|
||||
add_share_by_token(user, password, new_share, callback);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
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;
|
||||
|
||||
/** @type {HTMLHeadingElement} */ let title = /** @type {HTMLHeadingElement} */ (get_element(html_scene, "h1"));
|
||||
title.textContent = edit ? "Edit Share" : "New Share";
|
||||
submit_btn.textContent = edit ? "Save" : "Create";
|
||||
|
||||
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;
|
||||
|
||||
let initial_conversion = (edit && share && share.Conversion) ? share.Conversion : "none";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let displayname_key = get_property_key(collection.type, "DISPLAYNAME");
|
||||
if (!displayname_key) {
|
||||
if (displayname_override_enabled.parentElement) {
|
||||
displayname_override_enabled.parentElement.classList.add("hidden");
|
||||
}
|
||||
} else {
|
||||
if (displayname_override_enabled.parentElement) {
|
||||
displayname_override_enabled.parentElement.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
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._color_override_enabled.onchange = () => {
|
||||
this._color_override_input.disabled = !this._color_override_enabled.checked;
|
||||
};
|
||||
}
|
||||
|
||||
_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 != "none") {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
_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_conversion = conversion != "none";
|
||||
let enabled_by_owner = is_conversion ? true : this._enabled_checkbox.checked;
|
||||
let hidden_by_owner = is_conversion ? false : this._hidden_checkbox.checked;
|
||||
let permissions = is_conversion ? "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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
this._cancel_btn.onclick = () => this._oncancel();
|
||||
this._form.onsubmit = () => this._onsubmit();
|
||||
|
||||
/** @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";
|
||||
|
||||
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 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 {
|
||||
label.textContent = opt.charAt(0).toUpperCase() + opt.slice(1);
|
||||
}
|
||||
|
||||
this._conversions_container.appendChild(radio);
|
||||
this._conversions_container.appendChild(label);
|
||||
});
|
||||
|
||||
let is_addressbook = this._collection.type === CollectionType.ADDRESSBOOK;
|
||||
if (is_addressbook) {
|
||||
this._conversions_details.classList.remove("hidden");
|
||||
this._conversions_details.open = true;
|
||||
} else {
|
||||
this._conversions_details.classList.add("hidden");
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
this._displayname_override_enabled.checked = displayname_override_enabled_value;
|
||||
this._displayname_override_input.value = displayname;
|
||||
this._displayname_override_input.disabled = !displayname_override_enabled_value;
|
||||
|
||||
this._description_override_enabled.checked = description_override_enabled_value;
|
||||
this._description_override_input.value = description;
|
||||
this._description_override_input.disabled = !description_override_enabled_value;
|
||||
|
||||
this._color_override_enabled.checked = color_override_enabled_value;
|
||||
this._color_override_input.value = color;
|
||||
this._color_override_input.disabled = !color_override_enabled_value;
|
||||
|
||||
this._properties_fieldset.classList.remove("hidden");
|
||||
if (displayname_override_enabled_value || description_override_enabled_value || color_override_enabled_value) {
|
||||
this._properties_fieldset.open = 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");
|
||||
}
|
||||
} 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 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 {
|
||||
this._sharehref_input.value = random_uuid();
|
||||
}
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -42,103 +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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,136 +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.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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,5 @@ export class LoadingScene {
|
||||
this.html_scene.classList.add("hidden");
|
||||
}
|
||||
release() { }
|
||||
is_transient() { return true; }
|
||||
}
|
||||
@@ -31,165 +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.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();
|
||||
}
|
||||
}
|
||||
@@ -43,90 +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._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; }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,201 +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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,11 @@ export class Scene {
|
||||
* Scene is removed from scene stack.
|
||||
*/
|
||||
release() { }
|
||||
/**
|
||||
* Whether the scene should be excluded from browser history.
|
||||
* @returns boolean
|
||||
*/
|
||||
is_transient() { return false; }
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +49,87 @@ export class Scene {
|
||||
*/
|
||||
let scene_stack = [];
|
||||
|
||||
/** @type {Array<Array<Scene>>} */
|
||||
let history_array = [];
|
||||
let current_history_index = -1;
|
||||
let is_navigating_history = false;
|
||||
|
||||
function record_history() {
|
||||
if (is_navigating_history) return;
|
||||
|
||||
let current_scene = scene_stack.length > 0 ? scene_stack[scene_stack.length - 1] : null;
|
||||
if (current_scene && current_scene.is_transient && current_scene.is_transient()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare with current history to avoid duplicates
|
||||
if (history_array.length > 0 && current_history_index >= 0) {
|
||||
let last_stack = history_array[current_history_index];
|
||||
if (last_stack && last_stack.length === scene_stack.length) {
|
||||
let is_identical = true;
|
||||
for (let i = 0; i < scene_stack.length; i++) {
|
||||
if (last_stack[i] !== scene_stack[i]) {
|
||||
is_identical = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_identical) return;
|
||||
}
|
||||
}
|
||||
|
||||
history_array.splice(current_history_index + 1);
|
||||
history_array.push(scene_stack.slice());
|
||||
current_history_index++;
|
||||
|
||||
// Check if this is the first history entry we are recording
|
||||
if (typeof window !== "undefined" && window.history) {
|
||||
if (current_history_index === 0) {
|
||||
history.replaceState({ history_index: current_history_index }, '');
|
||||
} else {
|
||||
history.pushState({ history_index: current_history_index }, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined" && window.history) {
|
||||
if (!history.state || typeof history.state.history_index !== "number") {
|
||||
current_history_index = -1;
|
||||
} else {
|
||||
// If there's an existing state but we just loaded, we don't have the history_array memory.
|
||||
// We'll reset it. The user will be redirected to the root if they go back out of bounds.
|
||||
current_history_index = -1;
|
||||
}
|
||||
|
||||
window.addEventListener("popstate", (event) => {
|
||||
if (!event.state || typeof event.state.history_index !== "number") return;
|
||||
let new_index = event.state.history_index;
|
||||
|
||||
if (new_index >= 0 && new_index < history_array.length) {
|
||||
is_navigating_history = true;
|
||||
try {
|
||||
let new_stack = history_array[new_index];
|
||||
|
||||
if (scene_stack.length > 0) {
|
||||
scene_stack[scene_stack.length - 1].hide();
|
||||
}
|
||||
|
||||
scene_stack = new_stack.slice();
|
||||
|
||||
if (scene_stack.length > 0) {
|
||||
scene_stack[scene_stack.length - 1].show();
|
||||
}
|
||||
|
||||
current_history_index = new_index;
|
||||
} finally {
|
||||
is_navigating_history = false;
|
||||
}
|
||||
} else {
|
||||
// Out of bounds (e.g., from a previous session after a reload)
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Push scene onto stack.
|
||||
* @param {Scene} scene
|
||||
@@ -54,6 +140,7 @@ export function push_scene(scene) {
|
||||
}
|
||||
scene_stack.push(scene);
|
||||
scene.show();
|
||||
record_history();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,6 +167,7 @@ export function replace_scene(scene) {
|
||||
}
|
||||
scene_stack.push(scene);
|
||||
scene.show();
|
||||
record_history();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,6 +182,7 @@ export function pop_scene() {
|
||||
if (scene_stack.length >= 1) {
|
||||
scene_stack[scene_stack.length - 1].show();
|
||||
}
|
||||
record_history();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,6 +201,7 @@ export function pop_to_parent() {
|
||||
if (scene_stack.length >= 1) {
|
||||
scene_stack[scene_stack.length - 1].show();
|
||||
}
|
||||
record_history();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,6 +221,7 @@ export function pop_to_root() {
|
||||
if (scene_stack.length === 1) {
|
||||
scene_stack[0].show(); // Ensure the root scene is visible
|
||||
}
|
||||
record_history();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ export function displayPermissionsOrConversion(conversion, permissions, node) {
|
||||
const roElement = get_element(node, "[data-name=ro]");
|
||||
const rwElement = get_element(node, "[data-name=rw]");
|
||||
let fixedConversion = (conversion || "").toLowerCase();
|
||||
if (fixedConversion === "bday") {
|
||||
if (fixedConversion != "none" && fixedConversion != "") {
|
||||
conversionElement.classList.remove("hidden");
|
||||
roElement.classList.add("hidden");
|
||||
rwElement.classList.add("hidden");
|
||||
|
||||
Reference in New Issue
Block a user