From 16b5ce8c1b50bf2e2682e3b9593161f80888eec8 Mon Sep 17 00:00:00 2001 From: Erin Butler Date: Fri, 6 Dec 2024 16:56:50 -0500 Subject: [PATCH 1/5] Added attribute choices to the upload advanced section #1892 --- ui/src/css/text.css | 4 + ui/src/css/variables.css | 1 + .../inputs/feature/attribute-choices.js | 267 +++++++++ ui/src/js/components/upload-dialog-init.js | 381 +++++++++---- ui/src/js/components/upload-element.js | 524 +++++++++--------- ui/src/js/project-detail/section-upload.js | 14 +- 6 files changed, 811 insertions(+), 380 deletions(-) create mode 100644 ui/src/js/components/inputs/feature/attribute-choices.js diff --git a/ui/src/css/text.css b/ui/src/css/text.css index 897db548c..6e1ebba77 100644 --- a/ui/src/css/text.css +++ b/ui/src/css/text.css @@ -80,6 +80,10 @@ color: var(--color-white); } +.text-light-gray { + color: var(--color-gray--very-light); +} + .text-gray { color: var(--color-gray--light); } diff --git a/ui/src/css/variables.css b/ui/src/css/variables.css index 94bed6563..340fd5b62 100644 --- a/ui/src/css/variables.css +++ b/ui/src/css/variables.css @@ -30,6 +30,7 @@ --color-white--50: rgba(255, 255, 255, 0.5); --color-white--25: rgba(255, 255, 255, 0.25); + --color-gray--very-light: #d5dbe8; --color-gray--light: #a2afcd; --color-gray--medium: #72809d; --color-gray--dark: #6d7a96; /* rgb(109, 122, 150) */ diff --git a/ui/src/js/components/inputs/feature/attribute-choices.js b/ui/src/js/components/inputs/feature/attribute-choices.js new file mode 100644 index 000000000..48d3f8e15 --- /dev/null +++ b/ui/src/js/components/inputs/feature/attribute-choices.js @@ -0,0 +1,267 @@ +import { TatorElement } from "../../tator-element.js"; + +// This is a set of Object data +// You can specify the named properties each list item should have in it's object +export class AttributeChoices extends TatorElement { + constructor() { + super(); + + this._div = document.createElement("div"); + this._shadow.appendChild(this._div); + + this._enumDiv = document.createElement("div"); + this._enumDiv.classList.add( + "d-flex", + "flex-items-center", + "form-group", + "offset-xs-4" + ); + this._shadow.appendChild(this._enumDiv); + + this._attrEnum = document.createElement("enum-input"); + this._attrEnum.setAttribute("class", "col-12"); + this._attrEnum._select.classList.remove("col-8"); + this._attrEnum._select.classList.add("col-12"); + this._enumDiv.appendChild(this._attrEnum); + + this._attrEnum.addEventListener("change", () => { + this.dispatchEvent( + new CustomEvent("change", { + detail: { + name: this._attrEnum.getValue(), + }, + }) + ); + }); + + this.label = document.createElement("label"); + this.label.setAttribute( + "class", + "d-flex flex-justify-between flex-items-center py-1" + ); + this._div.appendChild(this.label); + + this._name = document.createTextNode(""); + this.label.appendChild(this._name); + + // + this._inputs = []; + this._rows = []; + + // Add new + this.addNewButton = this.addNewRow({ + labelText: "Set this", + callback: "", + }); + this._enumDiv.appendChild(this.addNewButton); + + this._properties = null; + this._emptyRow = null; + this._map = new Map(); + + this.addNewButton.addEventListener("click", (e) => { + e.preventDefault(); + this._newEmptyRow(); + }); + } + + static get observedAttributes() { + return ["name", "properties", "empty-row"]; + } + + attributeChangedCallback(name, oldValue, newValue) { + switch (name) { + case "name": + this._attrEnum._name.innerHTML = newValue; + this._name.innerHTML = "test"; + break; + case "properties": + this._properties = JSON.parse(newValue); + break; + case "empty-row": + this._emptyRow = JSON.parse(newValue); + break; + } + } + + set default(val) { + this._default = val; // this will be an array + } + + resetChoices() { + this._attrEnum.resetChoices(); + } + + set choices(val) { + this._map = new Map(); + this._attrEnum.choices = val; + + for (let type of val) { + this._map.set(type?.extra?.name, type.extra); + } + } + + reset() { + // Go back to default value + if (typeof this._default !== "undefined") { + this.setValue(this._default); + } else { + this.setValue([]); + } + } + + _newEmptyRow() { + let rowIndex = this._rows.length; + let row = this._newInputRow(this._emptyRow, rowIndex); + this._rows.push(row); + return this._div.appendChild(row); + } + + setValue(val) {} + + _newInputRow(val, rowIndex) { + const attrName = this._attrEnum.getValue(), + attrProperties = this._map.get(attrName); + // + let row = document.createElement("div"); + row.setAttribute("class", "d-flex text-gray flex-items-center"); + + if (attrProperties !== null) { + let propName = attrProperties.name; + let inputType = attrProperties.dtype; + let inputValue = attrProperties.default ? attrProperties.default : ""; + + let input = this._getInput( + inputType, + propName, + inputValue, + rowIndex, + attrProperties + ); + row.appendChild(input); + this._inputs[rowIndex] = input; + + const cancel = document.createElement("span"); + cancel.classList.add("f1", "clickable", "text-purple", "circle", "ml-2"); + cancel.innerText = "X"; + cancel.addEventListener("click", () => { + this._inputs.splice(rowIndex, 1); + input.remove(); + cancel.remove(); + const choicesSpliced = [...this._attrEnum._choices]; + choicesSpliced.push({ + value: propName, + label: propName, + extra: attrProperties, + }); + this._attrEnum.resetChoices(); + this.choices = choicesSpliced; + }); + row.appendChild(cancel); + } + return row; + } + + _getInput(dtype, inputKey, inputValue, rowIndex, attrProperties) { + let inputType = "text-input"; // string, number, float + switch (dtype) { + case "bool": + inputType = "bool-input"; + break; + case "enum": + inputType = "enum-input"; + break; + default: + inputType = "text-input"; + } + + this._attrEnum.resetChoices(); + this.choices = this._attrEnum._choices.filter((choice) => { + return inputKey !== choice.value; + }); + + let input = document.createElement(inputType); + input.setAttribute("name", inputKey); + + input.default = inputValue; + input.setAttribute("class", "col-12"); + + if (inputType === "enum-input") { + input.choices = attrProperties.choices.map((choice) => { + return { + value: choice, + label: choice, + extra: choice, + }; + }); + } + + input.setValue(inputValue); + + // input.addEventListener("change", () => { + // this.dispatchEvent(new Event("change")); + // }); + + // let keyobject = {}; + // keyobject[inputKey] = input; + // this._inputs[rowIndex].push(keyobject); + + return input; + } + + addNewRow({ labelText = "", callback = null } = {}) { + const labelWrap = document.createElement("label"); + labelWrap.setAttribute( + "class", + "f4 btn btn-charcoal btn-clear clickable btn-small col-2" + ); + + const spanTextNode = document.createElement("span"); + const spanText = document.createTextNode(""); + const labelDiv = document.createElement("div"); + + // spanTextNode.setAttribute("class", "col-sm-4 col-md-3 text-gray clickable"); + spanText.nodeValue = labelText; + spanTextNode.appendChild(spanText); + + labelWrap.append(spanTextNode); + + labelDiv.setAttribute("class", "py-2 f1 text-semibold"); + labelDiv.appendChild(labelWrap); + + return labelDiv; + } + + getValues() { + const attributes = {}; + + // Loop through the inputs + // # TODO this could be generalized + // # currently this is the only thing specific to algorith-edit.js + if (this._inputs.length > 0) { + for (let input of this._inputs) { + const name = input._name.innerText; + attributes[name] = input.getValue(); + } + } + + return attributes; + } + + changed() { + return this.getValue() !== this._default; + } + + clear() { + if (this._rows.length > 0) { + for (let x of this._rows) { + x.remove(); + } + + this._inputs = []; + this._rows = []; + } + } +} + +customElements.define("attribute-choices", AttributeChoices); diff --git a/ui/src/js/components/upload-dialog-init.js b/ui/src/js/components/upload-dialog-init.js index d855f9672..5d54afe21 100644 --- a/ui/src/js/components/upload-dialog-init.js +++ b/ui/src/js/components/upload-dialog-init.js @@ -1,120 +1,273 @@ import { ModalDialog } from "./modal-dialog.js"; +import "./inputs/feature/attribute-choices.js"; export class UploadDialogInit extends ModalDialog { - constructor() { - super(); - - this._title.nodeValue = "Upload Settings"; - - this._errors = document.createElement("ul"); - this._errors.setAttribute("class", "modal__errors d-flex flex-column"); - this._main.appendChild(this._errors); - - this._form = document.createElement("form"); - this._form.setAttribute("class", "modal__form"); - this._main.appendChild(this._form); - - const formGroup = document.createElement("div"); - formGroup.setAttribute("class", "form-group"); - this._form.appendChild(formGroup); - - this._parentFolders = document.createElement("enum-input"); - this._parentFolders.setAttribute("class", "text-gray f2"); - this._parentFolders.setAttribute("name", "Parent Folder:"); - formGroup.appendChild(this._parentFolders); - - this._imageType = document.createElement("enum-input"); - this._imageType.setAttribute("class", "text-gray f2"); - this._imageType.setAttribute("name", "Image Type:"); - this._imageType.permission = "View only"; - formGroup.appendChild(this._imageType); - - this._videoType = document.createElement("enum-input"); - this._videoType.setAttribute("class", "text-gray f2"); - this._videoType.setAttribute("name", "Video Type:"); - this._videoType.permission = "View only"; - formGroup.appendChild(this._videoType); - - const apply = document.createElement("button"); - apply.setAttribute("class", "btn btn-clear"); - apply.textContent = "Choose Files"; - this._footer.appendChild(apply); - - const cancel = document.createElement("button"); - cancel.setAttribute("class", "btn btn-clear btn-charcoal"); - cancel.textContent = "Cancel"; - this._footer.appendChild(cancel); - - cancel.addEventListener("click", this._closeCallback.bind(this)); - - apply.addEventListener("click", () => { - this._closeCallback(); - this.dispatchEvent(new CustomEvent("choose-files")); - }); - - // Data initialization - this._noParentName = "-- None --"; - this._sectionData = null; - } - - static get observedAttributes() { - return ModalDialog.observedAttributes; - } - - attributeChangedCallback(name, oldValue, newValue) { - ModalDialog.prototype.attributeChangedCallback.call( - this, - name, - oldValue, - newValue - ); - switch (name) { - case "is-open": - break; - } - } - - set mediaTypes(val) { - this._mediaTypes = val; - - this.setupTypeDropdown("image", this._imageType); - this.setupTypeDropdown("video", this._videoType); - } - - setupTypeDropdown(type, element) { - const list = []; - for (let t of this._mediaTypes) { - if (t.dtype === type) { - list.push({ value: t.id, label: t.name, extra: t }); - } - } - element.choices = list; - element.setValue(list[0].value); - - if (list.length >= 1) { - element.permission = "Can Edit"; - } - } - - open() { - this.setupData(); - this.setAttribute("is-open", "true"); - } - - setupData() { - const searchParams = new URLSearchParams(window.location.search), - selectedSection = searchParams.get("section"), - choices = this._sectionData.getFolderEnumChoices(); - - choices.unshift({ value: this._noParentName, label: this._noParentName }); - this._parentFolders.choices = choices; - - if (selectedSection) { - this._parentFolders.setValue(selectedSection); - } else { - this._parentFolders.setValue(this._noParentName); - } - } + constructor() { + super(); + + this._typeAttributeMap = new Map(); + + this._title.nodeValue = "Upload"; + this._modal.classList.add("fixed-height-scroll"); + + this._form = document.createElement("form"); + this._form.setAttribute("class", "modal__form"); + this._main.appendChild(this._form); + + const formGroup2 = document.createElement("div"); + formGroup2.setAttribute("class", "form-group"); + this._form.appendChild(formGroup2); + + this._parentFolders = document.createElement("enum-input"); + this._parentFolders.setAttribute("class", "text-gray f2"); + this._parentFolders.setAttribute("name", " Folder:"); + formGroup2.appendChild(this._parentFolders); + + this._mediaFormGroup = document.createElement("details"); + this._mediaFormGroup.setAttribute("class", "hidden form-group"); + this._form.appendChild(this._mediaFormGroup); + + this._helpText = document.createElement("summary"); + this._helpText.setAttribute( + "class", + "text-light-gray text-underline f2 clickable pt-3" + ); + + const closedText = ` + + + Advanced + `; + + const openText = ` + + + Advanced + `; + + const upArrow = ``; + + this._helpText.innerHTML = closedText; + + this._mediaFormGroup.addEventListener("toggle", (event) => { + if (this._mediaFormGroup.open) { + /* the element was toggled open */ + this._helpText.innerHTML = openText; + } else { + /* the element was toggled closed */ + this._helpText.innerHTML = closedText; + } + }); + + this._helpText0 = document.createElement("div"); + this._helpText0.setAttribute("class", "f3 text-gray py-3"); + this._helpText0.innerHTML = ` +

Specify the media type (useful when multiple types areconfigured).

+

Optionally, set an attribute value for this type during media creation.

+ `; + this._mediaFormGroup.appendChild(this._helpText0); + + this._mediaFormGroup.appendChild(this._helpText); + + this._helpText2 = document.createElement("p"); + this._helpText2.setAttribute("class", "text-gray f2 py-2"); + this._helpText2.innerText = ``; + this._mediaFormGroup.appendChild(this._helpText2); + + this._imageType = document.createElement("enum-input"); + this._imageType.setAttribute("class", "text-gray f2"); + this._imageType.setAttribute("name", "Image Type:"); + this._imageType.permission = "View only"; + this._mediaFormGroup.appendChild(this._imageType); + + this._imageAttributes = document.createElement("attribute-choices"); + this._imageAttributes.setAttribute("class", "text-gray f2 "); + this._imageAttributes.setAttribute("name", `${upArrow}`); + this._imageAttributes.permission = "View only"; + this._mediaFormGroup.appendChild(this._imageAttributes); + + this._videoType = document.createElement("enum-input"); + this._videoType.setAttribute( + "style", + "border-top: 1px solid var(--color-charcoal--light); margin-top: 15px;" + ); + this._videoType.setAttribute( + "class", + "text-gray f2 border-top pt-2 d-block" + ); + this._videoType.setAttribute("name", "Video Type:"); + this._videoType.permission = "View only"; + this._mediaFormGroup.appendChild(this._videoType); + + this._videoAttributes = document.createElement("attribute-choices"); + this._videoAttributes.setAttribute("class", "text-gray f2 "); + this._videoAttributes.setAttribute("name", `${upArrow}`); + this._videoAttributes.permission = "View only"; + this._mediaFormGroup.appendChild(this._videoAttributes); + + this._saveSettings = document.createElement("checkbox-input"); + this._saveSettings.setAttribute( + "class", + "text-light-gray pt-3 f2 d-block hidden" + ); + this._saveSettings.setAttribute( + "name", + "Save these settings for the duration of this session" + ); + this._saveSettings.setValue({ id: "save", checked: true }); + this._mediaFormGroup.appendChild(this._saveSettings); + + const apply = document.createElement("button"); + apply.setAttribute("class", "btn btn-clear"); + apply.textContent = "Choose Files"; + this._footer.appendChild(apply); + + const cancel = document.createElement("button"); + cancel.setAttribute("class", "btn btn-clear btn-charcoal"); + cancel.textContent = "Cancel"; + this._footer.appendChild(cancel); + + cancel.addEventListener("click", this._closeCallback.bind(this)); + + apply.addEventListener("click", () => { + this._closeCallback(); + this.dispatchEvent( + new CustomEvent("choose-files", { + detail: { + attrVideo: this._videoAttributes.getValues(), + attrImage: this._imageAttributes.getValues(), + }, + }) + ); + }); + + // Data initialization + this._noParentName = "-- None --"; + this._sectionData = null; + this._attrEnums = new Map(); + this._attrEnums.set("image", this._imageAttributes); + this._attrEnums.set("video", this._videoAttributes); + } + + static get observedAttributes() { + return ModalDialog.observedAttributes; + } + + attributeChangedCallback(name, oldValue, newValue) { + ModalDialog.prototype.attributeChangedCallback.call( + this, + name, + oldValue, + newValue + ); + switch (name) { + case "is-open": + break; + } + } + + set mediaTypes(val) { + this._mediaTypes = val; + this._typeAttributeMap = new Map(); + + const multipleImage = this.setupTypeDropdown("image", this._imageType); + const multipleVideo = this.setupTypeDropdown("video", this._videoType); + + // if (multipleImage || multipleVideo) { + this._mediaFormGroup.classList.remove("hidden"); + // } + } + + setupTypeDropdown(type, element) { + const list = []; + for (let t of this._mediaTypes) { + if (t.dtype === type) { + list.push({ value: t.id, label: `${t.name} (ID:${t.id})`, extra: t }); + this._typeAttributeMap.set(t.id, t.attribute_types); + } + } + + if (list && list.length > 0) { + list.sort((a, b) => { + if (a.label < b.label) { + return -1; + } + if (a.label > b.label) { + return 1; + } + return 0; + }); + + list.sort((a, b) => { + if (!a.extra.visible && b.extra.visible) { + return 1; + } + if (!b.extra.visible && a.extra.visible) { + return -1; + } + return 0; + }); + + list[0].label = `${list[0].label} - Default`; + element.choices = list; + element.setValue(list[0].value); + this._setAttrList(list[0].extra.attribute_types, type); + } + + element.addEventListener("change", (evt) => { + const value = Number(element.getValue()); + const attrInfo = this._typeAttributeMap.get(value); + console.log("Event ", value, this._typeAttributeMap, attrInfo); + // element.clear(); + this._setAttrList(attrInfo, type); + }); + + if (list.length > 0) { + element.permission = "Can Edit"; + } + + return list.length > 1; + } + + _setAttrList(attrList, type) { + const list = []; + console.log("Setting attr list", attrList, type); + if (attrList && attrList.length > 0) { + for (let attr of attrList) { + list.push({ + value: attr.name, + label: `${attr.name}`, + extra: attr, + }); + } + } + + if (this._attrEnums.has(type)) { + this._attrEnums.get(type).clear(); + this._attrEnums.get(type).resetChoices(); + this._attrEnums.get(type).choices = list; + } + } + + open() { + this.setupData(); + this.setAttribute("is-open", "true"); + } + + setupData() { + const searchParams = new URLSearchParams(window.location.search), + selectedSection = searchParams.get("section"), + choices = this._sectionData.getFolderEnumChoices(); + + choices.unshift({ value: this._noParentName, label: this._noParentName }); + this._parentFolders.choices = choices; + + if (selectedSection) { + this._parentFolders.setValue(selectedSection); + } else { + this._parentFolders.setValue(this._noParentName); + } + } } customElements.define("upload-dialog-init", UploadDialogInit); diff --git a/ui/src/js/components/upload-element.js b/ui/src/js/components/upload-element.js index 438f13e81..8f2f38fa4 100644 --- a/ui/src/js/components/upload-element.js +++ b/ui/src/js/components/upload-element.js @@ -3,296 +3,300 @@ import { v1 as uuidv1 } from "../../../node_modules/uuid/dist/esm-browser/index. import { uploadMedia } from "../../../../scripts/packages/tator-js/src/utils/upload-media.js"; export class UploadElement extends TatorElement { - constructor() { - super(); - this._fileSelectCallback = this._fileSelectCallback.bind(this); - this._haveNewSection = false; - this._abortController = new AbortController(); - this._cancel = false; - this._chosenSection = null; - this._chosenImageType = null; - this._chosenVideoType = null; - } + constructor() { + super(); + this._fileSelectCallback = this._fileSelectCallback.bind(this); + this._haveNewSection = false; + this._abortController = new AbortController(); + this._cancel = false; + this._chosenSection = null; + this._chosenImageType = null; + this._chosenVideoType = null; + this._videoAttr = {}; + this._imageAttr = {}; + } - init(store) { - this._store = store; - store.subscribe( - (state) => state.uploadCancelled, - (cancelled) => { - if (cancelled) { - // Cancel next uploads. - this._cancel = true; - // Abort uploads in progress. - this._abortController.abort(); - } else { - this._cancel = false; - } - } - ); - } + init(store) { + this._store = store; + store.subscribe( + (state) => state.uploadCancelled, + (cancelled) => { + if (cancelled) { + // Cancel next uploads. + this._cancel = true; + // Abort uploads in progress. + this._abortController.abort(); + } else { + this._cancel = false; + } + } + ); + } - _checkFile(file, gid) { - // File extension can have multiple components in archives - let comps = file.name.split(".").slice(-1); - let ext = comps.join("."); // rejoin extension + _checkFile(file, gid) { + // File extension can have multiple components in archives + let comps = file.name.split(".").slice(-1); + let ext = comps.join("."); // rejoin extension - const isImage = ext.match( - /(tiff|tif|bmp|jpe|jpg|jpeg|png|gif|avif|heic|heif)$/i - ); - const isVideo = ext.match( - /(mp4|avi|3gp|ogg|wmv|webm|flv|mkv|mov|mts|m4v|mpg|mp2|mpeg|mpe|mpv|m4p|qt|swf|avchd|ts)$/i - ); - const isArchive = ext.match(/^(zip|tar)/i); + const isImage = ext.match( + /(tiff|tif|bmp|jpe|jpg|jpeg|png|gif|avif|heic|heif)$/i + ); + const isVideo = ext.match( + /(mp4|avi|3gp|ogg|wmv|webm|flv|mkv|mov|mts|m4v|mpg|mp2|mpeg|mpe|mpv|m4p|qt|swf|avchd|ts)$/i + ); + const isArchive = ext.match(/^(zip|tar)/i); - let mediaType = null, - fileOk = false; + let mediaType = null, + fileOk = false, + attributes = null; - if ( - isImage && - this._chosenImageType !== null && - this._chosenImageType?.file_format === null - ) { - mediaType = this._chosenImageType; - } else if ( - isVideo && - this._chosenVideoType !== null && - this._chosenVideoType?.file_format === null - ) { - mediaType = this._chosenVideoType; - } + if ( + isImage && + this._chosenImageType !== null && + this._chosenImageType?.file_format === null + ) { + mediaType = this._chosenImageType; + } else if ( + isVideo && + this._chosenVideoType !== null && + this._chosenVideoType?.file_format === null + ) { + mediaType = this._chosenVideoType; + } - const mediaTypes = this._store.getState().mediaTypes; - for (let currentType of mediaTypes) { - if (mediaType === null) { - if (currentType.file_format === null) { - if (currentType.dtype == "image" && isImage) { - fileOk = true; - mediaType = currentType; - } else if (currentType.dtype == "video" && isVideo) { - fileOk = true; - mediaType = currentType; - } - } else { - fileOk = ext.toLowerCase() === currentType.file_format.toLowerCase(); - mediaType = currentType; + const mediaTypes = this._store.getState().mediaTypes; + for (let currentType of mediaTypes) { + if (mediaType === null) { + if (currentType.file_format === null) { + if (currentType.dtype == "image" && isImage) { + fileOk = true; + mediaType = currentType; + attributes = this._imageAttr; + } else if (currentType.dtype == "video" && isVideo) { + fileOk = true; + mediaType = currentType; + attributes = this._videoAttr; + } + } else { + fileOk = ext.toLowerCase() === currentType.file_format.toLowerCase(); + mediaType = currentType; - if (isArchive) { - fileOk = true; - } - } - } else { - if (Number(mediaType) === currentType.id) { - mediaType = currentType; - } - } - } + if (isArchive) { + fileOk = true; + } + } + } else { + if (Number(mediaType) === currentType.id) { + mediaType = currentType; + } + } + } - if (fileOk) { - function progressCallback(progress) { - this._store.setState({ uploadChunkProgress: progress }); - } - const fileInfo = { - file: file, - gid: gid, - mediaType: isArchive ? -1 : mediaType, - section: - this._section && !this._chosenSection - ? this._section - : this._chosenSection, - isImage: isImage, - isArchive: isArchive, - progressCallback: progressCallback.bind(this), - abortController: this._abortController, - }; - console.log("File is OK returning fileInfo", fileInfo); - return fileInfo; - } + if (fileOk) { + function progressCallback(progress) { + this._store.setState({ uploadChunkProgress: progress }); + } + const fileInfo = { + file: file, + gid: gid, + mediaType: isArchive ? -1 : mediaType, + section: + this._section && !this._chosenSection + ? this._section + : this._chosenSection, + isImage: isImage, + isArchive: isArchive, + progressCallback: progressCallback.bind(this), + abortController: this._abortController, + attributes: attributes ? attributes : {}, + }; + // console.log("File is OK", fileInfo); + return fileInfo; + } - this._store.setState({ - uploadError: `${file.name} is not a valid file type for this project!`, - }); - return false; - } + this._store.setState({ + uploadError: `${file.name} is not a valid file type for this project!`, + }); + return false; + } - // Iterates through items from drag and drop or file select - // event and sends them to the upload worker. - async _fileSelectCallback(ev) { - // Prevent browser default behavior. - ev.preventDefault(); + // Iterates through items from drag and drop or file select + // event and sends them to the upload worker. + async _fileSelectCallback(ev) { + // Prevent browser default behavior. + ev.preventDefault(); - // console.log(ev.target.files); + // Send immediate notification of adding files. + this.dispatchEvent(new Event("addingfiles", { composed: true })); - // Send immediate notification of adding files. - this.dispatchEvent(new Event("addingfiles", { composed: true })); + // Messages to send to store. + this._uploads = []; - // Messages to send to store. - this._uploads = []; + // Set a group ID on the upload. + const gid = uuidv1(); - // Set a group ID on the upload. - const gid = uuidv1(); + // Get main page. + const page = document.getElementsByTagName("project-detail")[0]; - // Get main page. - const page = document.getElementsByTagName("project-detail")[0]; + // Show loading gif. + const loading = document.createElement("img"); + loading.setAttribute("class", "loading"); + loading.setAttribute( + "src", + `${STATIC_PATH}/ui/src/images/tator_loading.gif` + ); + page._projects.appendChild(loading); + page.setAttribute("has-open-modal", ""); - // Show loading gif. - const loading = document.createElement("img"); - loading.setAttribute("class", "loading"); - loading.setAttribute( - "src", - `${STATIC_PATH}/ui/src/images/tator_loading.gif` - ); - page._projects.appendChild(loading); - page.setAttribute("has-open-modal", ""); + let numSkipped = 0; + let numStarted = 0; + let totalFiles = 0; + let totalSize = 0; + this._abortController = new AbortController(); + if (typeof ev.dataTransfer === "undefined") { + const files = ev.target.files; + totalFiles = files.length; + for (const file of files) { + const added = this._checkFile(file, gid); + if (added) { + this._uploads.push(added); + numStarted++; + totalSize += file.size; + } else { + numSkipped++; + } + } + } else { + const items = await getAllFileEntries(ev.dataTransfer.items); + for (const item of items) { + if (item.isFile) { + totalFiles++; + item.file((file) => { + const added = this._checkFile(file, gid); + if (added) { + this._uploads.push(added); + numStarted++; + totalSize += file.size; + } else { + numSkipped++; + } + }); + } + } + } + while (numSkipped + numStarted < totalFiles) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } - let numSkipped = 0; - let numStarted = 0; - let totalFiles = 0; - let totalSize = 0; - this._abortController = new AbortController(); - if (typeof ev.dataTransfer === "undefined") { - const files = ev.target.files; - totalFiles = files.length; - for (const file of files) { - const added = this._checkFile(file, gid); - if (added) { - this._uploads.push(added); - numStarted++; - totalSize += file.size; - } else { - numSkipped++; - } - } - } else { - const items = await getAllFileEntries(ev.dataTransfer.items); - for (const item of items) { - if (item.isFile) { - totalFiles++; - item.file((file) => { - const added = this._checkFile(file, gid); - if (added) { - this._uploads.push(added); - numStarted++; - totalSize += file.size; - } else { - numSkipped++; - } - }); - } - } - } - while (numSkipped + numStarted < totalFiles) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } + // Remove loading gif. + page._projects.removeChild(loading); + page.removeAttribute("has-open-modal", ""); - // Remove loading gif. - page._projects.removeChild(loading); - page.removeAttribute("has-open-modal", ""); + // If upload is big throw up a warning. + if (totalSize > 60000000000 || numStarted > 5000) { + const bigUpload = document.createElement("big-upload-form"); + const page = document.getElementsByTagName("project-detail")[0]; + page._projects.appendChild(bigUpload); + bigUpload.setAttribute("is-open", ""); + page.setAttribute("has-open-modal", ""); + bigUpload.addEventListener("close", (evt) => { + page.removeAttribute("has-open-modal", ""); + page._projects.removeChild(bigUpload); + }); + while (bigUpload.hasAttribute("is-open")) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + if (!bigUpload._confirm) { + page._leaveConfirmOk = false; + return; + } + } - // If upload is big throw up a warning. - if (totalSize > 60000000000 || numStarted > 5000) { - const bigUpload = document.createElement("big-upload-form"); - const page = document.getElementsByTagName("project-detail")[0]; - page._projects.appendChild(bigUpload); - bigUpload.setAttribute("is-open", ""); - page.setAttribute("has-open-modal", ""); - bigUpload.addEventListener("close", (evt) => { - page.removeAttribute("has-open-modal", ""); - page._projects.removeChild(bigUpload); - }); - while (bigUpload.hasAttribute("is-open")) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - if (!bigUpload._confirm) { - page._leaveConfirmOk = false; - return; - } - } - - // If no files were started, notify the user. - if (numStarted == 0 && totalFiles > 0) { - page._notify( - "Invalid files!", - "No files were added! Make sure selected files are valid for this project.", - "error" - ); - } else if (numStarted > 0) { - this.dispatchEvent( - new CustomEvent("filesadded", { - detail: { numSkipped: numSkipped, numStarted: numStarted }, - composed: true, - }) - ); - let promise = new Promise((resolve) => resolve(true)); - this._store.setState({ - uploadTotalFiles: this._uploads.length, - uploadCancelled: false, - }); - for (const [idx, msg] of this._uploads.entries()) { - promise = promise - .then(() => { - if (this._cancel) { - throw `Upload of ${msg.file.name} cancelled!`; - } - this._store.setState({ - uploadFilesComplete: idx, - uploadChunkProgress: 0, - uploadFilename: msg.file.name, - }); - return uploadMedia(msg.mediaType, msg.file, msg); - }) - .then(() => { - this._store.setState({ - uploadFilesComplete: idx + 1, - }); - }); - } - promise.catch((error) => { - this._store.setState({ uploadError: error.message }); - }); - } - } + // If no files were started, notify the user. + if (numStarted == 0 && totalFiles > 0) { + page._notify( + "Invalid files!", + "No files were added! Make sure selected files are valid for this project.", + "error" + ); + } else if (numStarted > 0) { + this.dispatchEvent( + new CustomEvent("filesadded", { + detail: { numSkipped: numSkipped, numStarted: numStarted }, + composed: true, + }) + ); + let promise = new Promise((resolve) => resolve(true)); + this._store.setState({ + uploadTotalFiles: this._uploads.length, + uploadCancelled: false, + }); + for (const [idx, msg] of this._uploads.entries()) { + promise = promise + .then(() => { + if (this._cancel) { + throw `Upload of ${msg.file.name} cancelled!`; + } + this._store.setState({ + uploadFilesComplete: idx, + uploadChunkProgress: 0, + uploadFilename: msg.file.name, + }); + return uploadMedia(msg.mediaType, msg.file, msg); + }) + .then(() => { + this._store.setState({ + uploadFilesComplete: idx + 1, + }); + }); + } + promise.catch((error) => { + this._store.setState({ uploadError: error.message }); + }); + } + } } // Drop handler function to get all files async function getAllFileEntries(dataTransferItemList) { - let fileEntries = []; - // Use BFS to traverse entire directory/file structure - let queue = []; - // Unfortunately dataTransferItemList is not iterable i.e. no forEach - for (let i = 0; i < dataTransferItemList.length; i++) { - queue.push(dataTransferItemList[i].webkitGetAsEntry()); - } - while (queue.length > 0) { - let entry = queue.shift(); - if (entry.isFile) { - fileEntries.push(entry); - } else if (entry.isDirectory) { - let reader = entry.createReader(); - queue.push(...(await readAllDirectoryEntries(reader))); - } - } - return fileEntries; + let fileEntries = []; + // Use BFS to traverse entire directory/file structure + let queue = []; + // Unfortunately dataTransferItemList is not iterable i.e. no forEach + for (let i = 0; i < dataTransferItemList.length; i++) { + queue.push(dataTransferItemList[i].webkitGetAsEntry()); + } + while (queue.length > 0) { + let entry = queue.shift(); + if (entry.isFile) { + fileEntries.push(entry); + } else if (entry.isDirectory) { + let reader = entry.createReader(); + queue.push(...(await readAllDirectoryEntries(reader))); + } + } + return fileEntries; } // Get all the entries (files or sub-directories) in a directory by calling // readEntries until it returns empty array async function readAllDirectoryEntries(directoryReader) { - let entries = []; - let readEntries = await readEntriesPromise(directoryReader); - while (readEntries.length > 0) { - entries.push(...readEntries); - readEntries = await readEntriesPromise(directoryReader); - } - return entries; + let entries = []; + let readEntries = await readEntriesPromise(directoryReader); + while (readEntries.length > 0) { + entries.push(...readEntries); + readEntries = await readEntriesPromise(directoryReader); + } + return entries; } // Wrap readEntries in a promise to make working with readEntries easier async function readEntriesPromise(directoryReader) { - try { - return await new Promise((resolve, reject) => { - directoryReader.readEntries(resolve, reject); - }); - } catch (err) { - console.log(err); - } + try { + return await new Promise((resolve, reject) => { + directoryReader.readEntries(resolve, reject); + }); + } catch (err) { + console.error(err); + } } diff --git a/ui/src/js/project-detail/section-upload.js b/ui/src/js/project-detail/section-upload.js index 4cc4e8ee4..8f925b44e 100644 --- a/ui/src/js/project-detail/section-upload.js +++ b/ui/src/js/project-detail/section-upload.js @@ -45,12 +45,14 @@ export class SectionUpload extends UploadElement { this.uploadDialogInit.open(); }); - this.uploadDialogInit.addEventListener("choose-files", () => { - this._chosenSection = this.uploadDialogInit._parentFolders.getValue(); - this._chosenImageType = this.uploadDialogInit._imageType.getValue(); - this._chosenVideoType = this.uploadDialogInit._videoType.getValue(); - this._fileInput.click(); - }); + this.uploadDialogInit.addEventListener("choose-files", (event) => { + this._fileInput.click(); + this._chosenSection = this.uploadDialogInit._parentFolders.getValue(); + this._chosenImageType = this.uploadDialogInit._imageType.getValue(); + this._chosenVideoType = this.uploadDialogInit._videoType.getValue(); + this._imageAttr = event.detail.attrImage; + this._videoAttr = event.detail.attrVideo; + }); } connectedCallback() { From 58ac9965bdfb3ff146624968197b1c7221272ce3 Mon Sep 17 00:00:00 2001 From: Erin Butler Date: Fri, 6 Dec 2024 16:58:13 -0500 Subject: [PATCH 2/5] Prettier write. --- .../inputs/feature/attribute-choices.js | 518 ++++++++--------- ui/src/js/components/upload-dialog-init.js | 484 ++++++++-------- ui/src/js/components/upload-element.js | 528 +++++++++--------- ui/src/js/project-detail/section-upload.js | 14 +- 4 files changed, 772 insertions(+), 772 deletions(-) diff --git a/ui/src/js/components/inputs/feature/attribute-choices.js b/ui/src/js/components/inputs/feature/attribute-choices.js index 48d3f8e15..2e1a30abc 100644 --- a/ui/src/js/components/inputs/feature/attribute-choices.js +++ b/ui/src/js/components/inputs/feature/attribute-choices.js @@ -3,265 +3,265 @@ import { TatorElement } from "../../tator-element.js"; // This is a set of Object data // You can specify the named properties each list item should have in it's object export class AttributeChoices extends TatorElement { - constructor() { - super(); - - this._div = document.createElement("div"); - this._shadow.appendChild(this._div); - - this._enumDiv = document.createElement("div"); - this._enumDiv.classList.add( - "d-flex", - "flex-items-center", - "form-group", - "offset-xs-4" - ); - this._shadow.appendChild(this._enumDiv); - - this._attrEnum = document.createElement("enum-input"); - this._attrEnum.setAttribute("class", "col-12"); - this._attrEnum._select.classList.remove("col-8"); - this._attrEnum._select.classList.add("col-12"); - this._enumDiv.appendChild(this._attrEnum); - - this._attrEnum.addEventListener("change", () => { - this.dispatchEvent( - new CustomEvent("change", { - detail: { - name: this._attrEnum.getValue(), - }, - }) - ); - }); - - this.label = document.createElement("label"); - this.label.setAttribute( - "class", - "d-flex flex-justify-between flex-items-center py-1" - ); - this._div.appendChild(this.label); - - this._name = document.createTextNode(""); - this.label.appendChild(this._name); - - // - this._inputs = []; - this._rows = []; - - // Add new - this.addNewButton = this.addNewRow({ - labelText: "Set this", - callback: "", - }); - this._enumDiv.appendChild(this.addNewButton); - - this._properties = null; - this._emptyRow = null; - this._map = new Map(); - - this.addNewButton.addEventListener("click", (e) => { - e.preventDefault(); - this._newEmptyRow(); - }); - } - - static get observedAttributes() { - return ["name", "properties", "empty-row"]; - } - - attributeChangedCallback(name, oldValue, newValue) { - switch (name) { - case "name": - this._attrEnum._name.innerHTML = newValue; - this._name.innerHTML = "test"; - break; - case "properties": - this._properties = JSON.parse(newValue); - break; - case "empty-row": - this._emptyRow = JSON.parse(newValue); - break; - } - } - - set default(val) { - this._default = val; // this will be an array - } - - resetChoices() { - this._attrEnum.resetChoices(); - } - - set choices(val) { - this._map = new Map(); - this._attrEnum.choices = val; - - for (let type of val) { - this._map.set(type?.extra?.name, type.extra); - } - } - - reset() { - // Go back to default value - if (typeof this._default !== "undefined") { - this.setValue(this._default); - } else { - this.setValue([]); - } - } - - _newEmptyRow() { - let rowIndex = this._rows.length; - let row = this._newInputRow(this._emptyRow, rowIndex); - this._rows.push(row); - return this._div.appendChild(row); - } - - setValue(val) {} - - _newInputRow(val, rowIndex) { - const attrName = this._attrEnum.getValue(), - attrProperties = this._map.get(attrName); - // - let row = document.createElement("div"); - row.setAttribute("class", "d-flex text-gray flex-items-center"); - - if (attrProperties !== null) { - let propName = attrProperties.name; - let inputType = attrProperties.dtype; - let inputValue = attrProperties.default ? attrProperties.default : ""; - - let input = this._getInput( - inputType, - propName, - inputValue, - rowIndex, - attrProperties - ); - row.appendChild(input); - this._inputs[rowIndex] = input; - - const cancel = document.createElement("span"); - cancel.classList.add("f1", "clickable", "text-purple", "circle", "ml-2"); - cancel.innerText = "X"; - cancel.addEventListener("click", () => { - this._inputs.splice(rowIndex, 1); - input.remove(); - cancel.remove(); - const choicesSpliced = [...this._attrEnum._choices]; - choicesSpliced.push({ - value: propName, - label: propName, - extra: attrProperties, - }); - this._attrEnum.resetChoices(); - this.choices = choicesSpliced; - }); - row.appendChild(cancel); - } - return row; - } - - _getInput(dtype, inputKey, inputValue, rowIndex, attrProperties) { - let inputType = "text-input"; // string, number, float - switch (dtype) { - case "bool": - inputType = "bool-input"; - break; - case "enum": - inputType = "enum-input"; - break; - default: - inputType = "text-input"; - } - - this._attrEnum.resetChoices(); - this.choices = this._attrEnum._choices.filter((choice) => { - return inputKey !== choice.value; - }); - - let input = document.createElement(inputType); - input.setAttribute("name", inputKey); - - input.default = inputValue; - input.setAttribute("class", "col-12"); - - if (inputType === "enum-input") { - input.choices = attrProperties.choices.map((choice) => { - return { - value: choice, - label: choice, - extra: choice, - }; - }); - } - - input.setValue(inputValue); - - // input.addEventListener("change", () => { - // this.dispatchEvent(new Event("change")); - // }); - - // let keyobject = {}; - // keyobject[inputKey] = input; - // this._inputs[rowIndex].push(keyobject); - - return input; - } - - addNewRow({ labelText = "", callback = null } = {}) { - const labelWrap = document.createElement("label"); - labelWrap.setAttribute( - "class", - "f4 btn btn-charcoal btn-clear clickable btn-small col-2" - ); - - const spanTextNode = document.createElement("span"); - const spanText = document.createTextNode(""); - const labelDiv = document.createElement("div"); - - // spanTextNode.setAttribute("class", "col-sm-4 col-md-3 text-gray clickable"); - spanText.nodeValue = labelText; - spanTextNode.appendChild(spanText); - - labelWrap.append(spanTextNode); - - labelDiv.setAttribute("class", "py-2 f1 text-semibold"); - labelDiv.appendChild(labelWrap); - - return labelDiv; - } - - getValues() { - const attributes = {}; - - // Loop through the inputs - // # TODO this could be generalized - // # currently this is the only thing specific to algorith-edit.js - if (this._inputs.length > 0) { - for (let input of this._inputs) { - const name = input._name.innerText; - attributes[name] = input.getValue(); - } - } - - return attributes; - } - - changed() { - return this.getValue() !== this._default; - } - - clear() { - if (this._rows.length > 0) { - for (let x of this._rows) { - x.remove(); - } - - this._inputs = []; - this._rows = []; - } - } + constructor() { + super(); + + this._div = document.createElement("div"); + this._shadow.appendChild(this._div); + + this._enumDiv = document.createElement("div"); + this._enumDiv.classList.add( + "d-flex", + "flex-items-center", + "form-group", + "offset-xs-4" + ); + this._shadow.appendChild(this._enumDiv); + + this._attrEnum = document.createElement("enum-input"); + this._attrEnum.setAttribute("class", "col-12"); + this._attrEnum._select.classList.remove("col-8"); + this._attrEnum._select.classList.add("col-12"); + this._enumDiv.appendChild(this._attrEnum); + + this._attrEnum.addEventListener("change", () => { + this.dispatchEvent( + new CustomEvent("change", { + detail: { + name: this._attrEnum.getValue(), + }, + }) + ); + }); + + this.label = document.createElement("label"); + this.label.setAttribute( + "class", + "d-flex flex-justify-between flex-items-center py-1" + ); + this._div.appendChild(this.label); + + this._name = document.createTextNode(""); + this.label.appendChild(this._name); + + // + this._inputs = []; + this._rows = []; + + // Add new + this.addNewButton = this.addNewRow({ + labelText: "Set this", + callback: "", + }); + this._enumDiv.appendChild(this.addNewButton); + + this._properties = null; + this._emptyRow = null; + this._map = new Map(); + + this.addNewButton.addEventListener("click", (e) => { + e.preventDefault(); + this._newEmptyRow(); + }); + } + + static get observedAttributes() { + return ["name", "properties", "empty-row"]; + } + + attributeChangedCallback(name, oldValue, newValue) { + switch (name) { + case "name": + this._attrEnum._name.innerHTML = newValue; + this._name.innerHTML = "test"; + break; + case "properties": + this._properties = JSON.parse(newValue); + break; + case "empty-row": + this._emptyRow = JSON.parse(newValue); + break; + } + } + + set default(val) { + this._default = val; // this will be an array + } + + resetChoices() { + this._attrEnum.resetChoices(); + } + + set choices(val) { + this._map = new Map(); + this._attrEnum.choices = val; + + for (let type of val) { + this._map.set(type?.extra?.name, type.extra); + } + } + + reset() { + // Go back to default value + if (typeof this._default !== "undefined") { + this.setValue(this._default); + } else { + this.setValue([]); + } + } + + _newEmptyRow() { + let rowIndex = this._rows.length; + let row = this._newInputRow(this._emptyRow, rowIndex); + this._rows.push(row); + return this._div.appendChild(row); + } + + setValue(val) {} + + _newInputRow(val, rowIndex) { + const attrName = this._attrEnum.getValue(), + attrProperties = this._map.get(attrName); + // + let row = document.createElement("div"); + row.setAttribute("class", "d-flex text-gray flex-items-center"); + + if (attrProperties !== null) { + let propName = attrProperties.name; + let inputType = attrProperties.dtype; + let inputValue = attrProperties.default ? attrProperties.default : ""; + + let input = this._getInput( + inputType, + propName, + inputValue, + rowIndex, + attrProperties + ); + row.appendChild(input); + this._inputs[rowIndex] = input; + + const cancel = document.createElement("span"); + cancel.classList.add("f1", "clickable", "text-purple", "circle", "ml-2"); + cancel.innerText = "X"; + cancel.addEventListener("click", () => { + this._inputs.splice(rowIndex, 1); + input.remove(); + cancel.remove(); + const choicesSpliced = [...this._attrEnum._choices]; + choicesSpliced.push({ + value: propName, + label: propName, + extra: attrProperties, + }); + this._attrEnum.resetChoices(); + this.choices = choicesSpliced; + }); + row.appendChild(cancel); + } + return row; + } + + _getInput(dtype, inputKey, inputValue, rowIndex, attrProperties) { + let inputType = "text-input"; // string, number, float + switch (dtype) { + case "bool": + inputType = "bool-input"; + break; + case "enum": + inputType = "enum-input"; + break; + default: + inputType = "text-input"; + } + + this._attrEnum.resetChoices(); + this.choices = this._attrEnum._choices.filter((choice) => { + return inputKey !== choice.value; + }); + + let input = document.createElement(inputType); + input.setAttribute("name", inputKey); + + input.default = inputValue; + input.setAttribute("class", "col-12"); + + if (inputType === "enum-input") { + input.choices = attrProperties.choices.map((choice) => { + return { + value: choice, + label: choice, + extra: choice, + }; + }); + } + + input.setValue(inputValue); + + // input.addEventListener("change", () => { + // this.dispatchEvent(new Event("change")); + // }); + + // let keyobject = {}; + // keyobject[inputKey] = input; + // this._inputs[rowIndex].push(keyobject); + + return input; + } + + addNewRow({ labelText = "", callback = null } = {}) { + const labelWrap = document.createElement("label"); + labelWrap.setAttribute( + "class", + "f4 btn btn-charcoal btn-clear clickable btn-small col-2" + ); + + const spanTextNode = document.createElement("span"); + const spanText = document.createTextNode(""); + const labelDiv = document.createElement("div"); + + // spanTextNode.setAttribute("class", "col-sm-4 col-md-3 text-gray clickable"); + spanText.nodeValue = labelText; + spanTextNode.appendChild(spanText); + + labelWrap.append(spanTextNode); + + labelDiv.setAttribute("class", "py-2 f1 text-semibold"); + labelDiv.appendChild(labelWrap); + + return labelDiv; + } + + getValues() { + const attributes = {}; + + // Loop through the inputs + // # TODO this could be generalized + // # currently this is the only thing specific to algorith-edit.js + if (this._inputs.length > 0) { + for (let input of this._inputs) { + const name = input._name.innerText; + attributes[name] = input.getValue(); + } + } + + return attributes; + } + + changed() { + return this.getValue() !== this._default; + } + + clear() { + if (this._rows.length > 0) { + for (let x of this._rows) { + x.remove(); + } + + this._inputs = []; + this._rows = []; + } + } } customElements.define("attribute-choices", AttributeChoices); diff --git a/ui/src/js/components/upload-dialog-init.js b/ui/src/js/components/upload-dialog-init.js index 5d54afe21..11df931a4 100644 --- a/ui/src/js/components/upload-dialog-init.js +++ b/ui/src/js/components/upload-dialog-init.js @@ -2,272 +2,272 @@ import { ModalDialog } from "./modal-dialog.js"; import "./inputs/feature/attribute-choices.js"; export class UploadDialogInit extends ModalDialog { - constructor() { - super(); + constructor() { + super(); - this._typeAttributeMap = new Map(); + this._typeAttributeMap = new Map(); - this._title.nodeValue = "Upload"; - this._modal.classList.add("fixed-height-scroll"); + this._title.nodeValue = "Upload"; + this._modal.classList.add("fixed-height-scroll"); - this._form = document.createElement("form"); - this._form.setAttribute("class", "modal__form"); - this._main.appendChild(this._form); + this._form = document.createElement("form"); + this._form.setAttribute("class", "modal__form"); + this._main.appendChild(this._form); - const formGroup2 = document.createElement("div"); - formGroup2.setAttribute("class", "form-group"); - this._form.appendChild(formGroup2); + const formGroup2 = document.createElement("div"); + formGroup2.setAttribute("class", "form-group"); + this._form.appendChild(formGroup2); - this._parentFolders = document.createElement("enum-input"); - this._parentFolders.setAttribute("class", "text-gray f2"); - this._parentFolders.setAttribute("name", " Folder:"); - formGroup2.appendChild(this._parentFolders); + this._parentFolders = document.createElement("enum-input"); + this._parentFolders.setAttribute("class", "text-gray f2"); + this._parentFolders.setAttribute("name", " Folder:"); + formGroup2.appendChild(this._parentFolders); - this._mediaFormGroup = document.createElement("details"); - this._mediaFormGroup.setAttribute("class", "hidden form-group"); - this._form.appendChild(this._mediaFormGroup); + this._mediaFormGroup = document.createElement("details"); + this._mediaFormGroup.setAttribute("class", "hidden form-group"); + this._form.appendChild(this._mediaFormGroup); - this._helpText = document.createElement("summary"); - this._helpText.setAttribute( - "class", - "text-light-gray text-underline f2 clickable pt-3" - ); + this._helpText = document.createElement("summary"); + this._helpText.setAttribute( + "class", + "text-light-gray text-underline f2 clickable pt-3" + ); - const closedText = ` + const closedText = ` Advanced `; - const openText = ` + const openText = ` Advanced `; - const upArrow = ``; + const upArrow = ``; - this._helpText.innerHTML = closedText; + this._helpText.innerHTML = closedText; - this._mediaFormGroup.addEventListener("toggle", (event) => { - if (this._mediaFormGroup.open) { - /* the element was toggled open */ - this._helpText.innerHTML = openText; - } else { - /* the element was toggled closed */ - this._helpText.innerHTML = closedText; - } - }); + this._mediaFormGroup.addEventListener("toggle", (event) => { + if (this._mediaFormGroup.open) { + /* the element was toggled open */ + this._helpText.innerHTML = openText; + } else { + /* the element was toggled closed */ + this._helpText.innerHTML = closedText; + } + }); - this._helpText0 = document.createElement("div"); - this._helpText0.setAttribute("class", "f3 text-gray py-3"); - this._helpText0.innerHTML = ` + this._helpText0 = document.createElement("div"); + this._helpText0.setAttribute("class", "f3 text-gray py-3"); + this._helpText0.innerHTML = `

Specify the media type (useful when multiple types areconfigured).

Optionally, set an attribute value for this type during media creation.

`; - this._mediaFormGroup.appendChild(this._helpText0); - - this._mediaFormGroup.appendChild(this._helpText); - - this._helpText2 = document.createElement("p"); - this._helpText2.setAttribute("class", "text-gray f2 py-2"); - this._helpText2.innerText = ``; - this._mediaFormGroup.appendChild(this._helpText2); - - this._imageType = document.createElement("enum-input"); - this._imageType.setAttribute("class", "text-gray f2"); - this._imageType.setAttribute("name", "Image Type:"); - this._imageType.permission = "View only"; - this._mediaFormGroup.appendChild(this._imageType); - - this._imageAttributes = document.createElement("attribute-choices"); - this._imageAttributes.setAttribute("class", "text-gray f2 "); - this._imageAttributes.setAttribute("name", `${upArrow}`); - this._imageAttributes.permission = "View only"; - this._mediaFormGroup.appendChild(this._imageAttributes); - - this._videoType = document.createElement("enum-input"); - this._videoType.setAttribute( - "style", - "border-top: 1px solid var(--color-charcoal--light); margin-top: 15px;" - ); - this._videoType.setAttribute( - "class", - "text-gray f2 border-top pt-2 d-block" - ); - this._videoType.setAttribute("name", "Video Type:"); - this._videoType.permission = "View only"; - this._mediaFormGroup.appendChild(this._videoType); - - this._videoAttributes = document.createElement("attribute-choices"); - this._videoAttributes.setAttribute("class", "text-gray f2 "); - this._videoAttributes.setAttribute("name", `${upArrow}`); - this._videoAttributes.permission = "View only"; - this._mediaFormGroup.appendChild(this._videoAttributes); - - this._saveSettings = document.createElement("checkbox-input"); - this._saveSettings.setAttribute( - "class", - "text-light-gray pt-3 f2 d-block hidden" - ); - this._saveSettings.setAttribute( - "name", - "Save these settings for the duration of this session" - ); - this._saveSettings.setValue({ id: "save", checked: true }); - this._mediaFormGroup.appendChild(this._saveSettings); - - const apply = document.createElement("button"); - apply.setAttribute("class", "btn btn-clear"); - apply.textContent = "Choose Files"; - this._footer.appendChild(apply); - - const cancel = document.createElement("button"); - cancel.setAttribute("class", "btn btn-clear btn-charcoal"); - cancel.textContent = "Cancel"; - this._footer.appendChild(cancel); - - cancel.addEventListener("click", this._closeCallback.bind(this)); - - apply.addEventListener("click", () => { - this._closeCallback(); - this.dispatchEvent( - new CustomEvent("choose-files", { - detail: { - attrVideo: this._videoAttributes.getValues(), - attrImage: this._imageAttributes.getValues(), - }, - }) - ); - }); - - // Data initialization - this._noParentName = "-- None --"; - this._sectionData = null; - this._attrEnums = new Map(); - this._attrEnums.set("image", this._imageAttributes); - this._attrEnums.set("video", this._videoAttributes); - } - - static get observedAttributes() { - return ModalDialog.observedAttributes; - } - - attributeChangedCallback(name, oldValue, newValue) { - ModalDialog.prototype.attributeChangedCallback.call( - this, - name, - oldValue, - newValue - ); - switch (name) { - case "is-open": - break; - } - } - - set mediaTypes(val) { - this._mediaTypes = val; - this._typeAttributeMap = new Map(); - - const multipleImage = this.setupTypeDropdown("image", this._imageType); - const multipleVideo = this.setupTypeDropdown("video", this._videoType); - - // if (multipleImage || multipleVideo) { - this._mediaFormGroup.classList.remove("hidden"); - // } - } - - setupTypeDropdown(type, element) { - const list = []; - for (let t of this._mediaTypes) { - if (t.dtype === type) { - list.push({ value: t.id, label: `${t.name} (ID:${t.id})`, extra: t }); - this._typeAttributeMap.set(t.id, t.attribute_types); - } - } - - if (list && list.length > 0) { - list.sort((a, b) => { - if (a.label < b.label) { - return -1; - } - if (a.label > b.label) { - return 1; - } - return 0; - }); - - list.sort((a, b) => { - if (!a.extra.visible && b.extra.visible) { - return 1; - } - if (!b.extra.visible && a.extra.visible) { - return -1; - } - return 0; - }); - - list[0].label = `${list[0].label} - Default`; - element.choices = list; - element.setValue(list[0].value); - this._setAttrList(list[0].extra.attribute_types, type); - } - - element.addEventListener("change", (evt) => { - const value = Number(element.getValue()); - const attrInfo = this._typeAttributeMap.get(value); - console.log("Event ", value, this._typeAttributeMap, attrInfo); - // element.clear(); - this._setAttrList(attrInfo, type); - }); - - if (list.length > 0) { - element.permission = "Can Edit"; - } - - return list.length > 1; - } - - _setAttrList(attrList, type) { - const list = []; - console.log("Setting attr list", attrList, type); - if (attrList && attrList.length > 0) { - for (let attr of attrList) { - list.push({ - value: attr.name, - label: `${attr.name}`, - extra: attr, - }); - } - } - - if (this._attrEnums.has(type)) { - this._attrEnums.get(type).clear(); - this._attrEnums.get(type).resetChoices(); - this._attrEnums.get(type).choices = list; - } - } - - open() { - this.setupData(); - this.setAttribute("is-open", "true"); - } - - setupData() { - const searchParams = new URLSearchParams(window.location.search), - selectedSection = searchParams.get("section"), - choices = this._sectionData.getFolderEnumChoices(); - - choices.unshift({ value: this._noParentName, label: this._noParentName }); - this._parentFolders.choices = choices; - - if (selectedSection) { - this._parentFolders.setValue(selectedSection); - } else { - this._parentFolders.setValue(this._noParentName); - } - } + this._mediaFormGroup.appendChild(this._helpText0); + + this._mediaFormGroup.appendChild(this._helpText); + + this._helpText2 = document.createElement("p"); + this._helpText2.setAttribute("class", "text-gray f2 py-2"); + this._helpText2.innerText = ``; + this._mediaFormGroup.appendChild(this._helpText2); + + this._imageType = document.createElement("enum-input"); + this._imageType.setAttribute("class", "text-gray f2"); + this._imageType.setAttribute("name", "Image Type:"); + this._imageType.permission = "View only"; + this._mediaFormGroup.appendChild(this._imageType); + + this._imageAttributes = document.createElement("attribute-choices"); + this._imageAttributes.setAttribute("class", "text-gray f2 "); + this._imageAttributes.setAttribute("name", `${upArrow}`); + this._imageAttributes.permission = "View only"; + this._mediaFormGroup.appendChild(this._imageAttributes); + + this._videoType = document.createElement("enum-input"); + this._videoType.setAttribute( + "style", + "border-top: 1px solid var(--color-charcoal--light); margin-top: 15px;" + ); + this._videoType.setAttribute( + "class", + "text-gray f2 border-top pt-2 d-block" + ); + this._videoType.setAttribute("name", "Video Type:"); + this._videoType.permission = "View only"; + this._mediaFormGroup.appendChild(this._videoType); + + this._videoAttributes = document.createElement("attribute-choices"); + this._videoAttributes.setAttribute("class", "text-gray f2 "); + this._videoAttributes.setAttribute("name", `${upArrow}`); + this._videoAttributes.permission = "View only"; + this._mediaFormGroup.appendChild(this._videoAttributes); + + this._saveSettings = document.createElement("checkbox-input"); + this._saveSettings.setAttribute( + "class", + "text-light-gray pt-3 f2 d-block hidden" + ); + this._saveSettings.setAttribute( + "name", + "Save these settings for the duration of this session" + ); + this._saveSettings.setValue({ id: "save", checked: true }); + this._mediaFormGroup.appendChild(this._saveSettings); + + const apply = document.createElement("button"); + apply.setAttribute("class", "btn btn-clear"); + apply.textContent = "Choose Files"; + this._footer.appendChild(apply); + + const cancel = document.createElement("button"); + cancel.setAttribute("class", "btn btn-clear btn-charcoal"); + cancel.textContent = "Cancel"; + this._footer.appendChild(cancel); + + cancel.addEventListener("click", this._closeCallback.bind(this)); + + apply.addEventListener("click", () => { + this._closeCallback(); + this.dispatchEvent( + new CustomEvent("choose-files", { + detail: { + attrVideo: this._videoAttributes.getValues(), + attrImage: this._imageAttributes.getValues(), + }, + }) + ); + }); + + // Data initialization + this._noParentName = "-- None --"; + this._sectionData = null; + this._attrEnums = new Map(); + this._attrEnums.set("image", this._imageAttributes); + this._attrEnums.set("video", this._videoAttributes); + } + + static get observedAttributes() { + return ModalDialog.observedAttributes; + } + + attributeChangedCallback(name, oldValue, newValue) { + ModalDialog.prototype.attributeChangedCallback.call( + this, + name, + oldValue, + newValue + ); + switch (name) { + case "is-open": + break; + } + } + + set mediaTypes(val) { + this._mediaTypes = val; + this._typeAttributeMap = new Map(); + + const multipleImage = this.setupTypeDropdown("image", this._imageType); + const multipleVideo = this.setupTypeDropdown("video", this._videoType); + + // if (multipleImage || multipleVideo) { + this._mediaFormGroup.classList.remove("hidden"); + // } + } + + setupTypeDropdown(type, element) { + const list = []; + for (let t of this._mediaTypes) { + if (t.dtype === type) { + list.push({ value: t.id, label: `${t.name} (ID:${t.id})`, extra: t }); + this._typeAttributeMap.set(t.id, t.attribute_types); + } + } + + if (list && list.length > 0) { + list.sort((a, b) => { + if (a.label < b.label) { + return -1; + } + if (a.label > b.label) { + return 1; + } + return 0; + }); + + list.sort((a, b) => { + if (!a.extra.visible && b.extra.visible) { + return 1; + } + if (!b.extra.visible && a.extra.visible) { + return -1; + } + return 0; + }); + + list[0].label = `${list[0].label} - Default`; + element.choices = list; + element.setValue(list[0].value); + this._setAttrList(list[0].extra.attribute_types, type); + } + + element.addEventListener("change", (evt) => { + const value = Number(element.getValue()); + const attrInfo = this._typeAttributeMap.get(value); + console.log("Event ", value, this._typeAttributeMap, attrInfo); + // element.clear(); + this._setAttrList(attrInfo, type); + }); + + if (list.length > 0) { + element.permission = "Can Edit"; + } + + return list.length > 1; + } + + _setAttrList(attrList, type) { + const list = []; + console.log("Setting attr list", attrList, type); + if (attrList && attrList.length > 0) { + for (let attr of attrList) { + list.push({ + value: attr.name, + label: `${attr.name}`, + extra: attr, + }); + } + } + + if (this._attrEnums.has(type)) { + this._attrEnums.get(type).clear(); + this._attrEnums.get(type).resetChoices(); + this._attrEnums.get(type).choices = list; + } + } + + open() { + this.setupData(); + this.setAttribute("is-open", "true"); + } + + setupData() { + const searchParams = new URLSearchParams(window.location.search), + selectedSection = searchParams.get("section"), + choices = this._sectionData.getFolderEnumChoices(); + + choices.unshift({ value: this._noParentName, label: this._noParentName }); + this._parentFolders.choices = choices; + + if (selectedSection) { + this._parentFolders.setValue(selectedSection); + } else { + this._parentFolders.setValue(this._noParentName); + } + } } customElements.define("upload-dialog-init", UploadDialogInit); diff --git a/ui/src/js/components/upload-element.js b/ui/src/js/components/upload-element.js index 8f2f38fa4..4527dca22 100644 --- a/ui/src/js/components/upload-element.js +++ b/ui/src/js/components/upload-element.js @@ -3,300 +3,300 @@ import { v1 as uuidv1 } from "../../../node_modules/uuid/dist/esm-browser/index. import { uploadMedia } from "../../../../scripts/packages/tator-js/src/utils/upload-media.js"; export class UploadElement extends TatorElement { - constructor() { - super(); - this._fileSelectCallback = this._fileSelectCallback.bind(this); - this._haveNewSection = false; - this._abortController = new AbortController(); - this._cancel = false; - this._chosenSection = null; - this._chosenImageType = null; - this._chosenVideoType = null; - this._videoAttr = {}; - this._imageAttr = {}; - } + constructor() { + super(); + this._fileSelectCallback = this._fileSelectCallback.bind(this); + this._haveNewSection = false; + this._abortController = new AbortController(); + this._cancel = false; + this._chosenSection = null; + this._chosenImageType = null; + this._chosenVideoType = null; + this._videoAttr = {}; + this._imageAttr = {}; + } - init(store) { - this._store = store; - store.subscribe( - (state) => state.uploadCancelled, - (cancelled) => { - if (cancelled) { - // Cancel next uploads. - this._cancel = true; - // Abort uploads in progress. - this._abortController.abort(); - } else { - this._cancel = false; - } - } - ); - } + init(store) { + this._store = store; + store.subscribe( + (state) => state.uploadCancelled, + (cancelled) => { + if (cancelled) { + // Cancel next uploads. + this._cancel = true; + // Abort uploads in progress. + this._abortController.abort(); + } else { + this._cancel = false; + } + } + ); + } - _checkFile(file, gid) { - // File extension can have multiple components in archives - let comps = file.name.split(".").slice(-1); - let ext = comps.join("."); // rejoin extension + _checkFile(file, gid) { + // File extension can have multiple components in archives + let comps = file.name.split(".").slice(-1); + let ext = comps.join("."); // rejoin extension - const isImage = ext.match( - /(tiff|tif|bmp|jpe|jpg|jpeg|png|gif|avif|heic|heif)$/i - ); - const isVideo = ext.match( - /(mp4|avi|3gp|ogg|wmv|webm|flv|mkv|mov|mts|m4v|mpg|mp2|mpeg|mpe|mpv|m4p|qt|swf|avchd|ts)$/i - ); - const isArchive = ext.match(/^(zip|tar)/i); + const isImage = ext.match( + /(tiff|tif|bmp|jpe|jpg|jpeg|png|gif|avif|heic|heif)$/i + ); + const isVideo = ext.match( + /(mp4|avi|3gp|ogg|wmv|webm|flv|mkv|mov|mts|m4v|mpg|mp2|mpeg|mpe|mpv|m4p|qt|swf|avchd|ts)$/i + ); + const isArchive = ext.match(/^(zip|tar)/i); - let mediaType = null, - fileOk = false, - attributes = null; + let mediaType = null, + fileOk = false, + attributes = null; - if ( - isImage && - this._chosenImageType !== null && - this._chosenImageType?.file_format === null - ) { - mediaType = this._chosenImageType; - } else if ( - isVideo && - this._chosenVideoType !== null && - this._chosenVideoType?.file_format === null - ) { - mediaType = this._chosenVideoType; - } + if ( + isImage && + this._chosenImageType !== null && + this._chosenImageType?.file_format === null + ) { + mediaType = this._chosenImageType; + } else if ( + isVideo && + this._chosenVideoType !== null && + this._chosenVideoType?.file_format === null + ) { + mediaType = this._chosenVideoType; + } - const mediaTypes = this._store.getState().mediaTypes; - for (let currentType of mediaTypes) { - if (mediaType === null) { - if (currentType.file_format === null) { - if (currentType.dtype == "image" && isImage) { - fileOk = true; - mediaType = currentType; - attributes = this._imageAttr; - } else if (currentType.dtype == "video" && isVideo) { - fileOk = true; - mediaType = currentType; - attributes = this._videoAttr; - } - } else { - fileOk = ext.toLowerCase() === currentType.file_format.toLowerCase(); - mediaType = currentType; + const mediaTypes = this._store.getState().mediaTypes; + for (let currentType of mediaTypes) { + if (mediaType === null) { + if (currentType.file_format === null) { + if (currentType.dtype == "image" && isImage) { + fileOk = true; + mediaType = currentType; + attributes = this._imageAttr; + } else if (currentType.dtype == "video" && isVideo) { + fileOk = true; + mediaType = currentType; + attributes = this._videoAttr; + } + } else { + fileOk = ext.toLowerCase() === currentType.file_format.toLowerCase(); + mediaType = currentType; - if (isArchive) { - fileOk = true; - } - } - } else { - if (Number(mediaType) === currentType.id) { - mediaType = currentType; - } - } - } + if (isArchive) { + fileOk = true; + } + } + } else { + if (Number(mediaType) === currentType.id) { + mediaType = currentType; + } + } + } - if (fileOk) { - function progressCallback(progress) { - this._store.setState({ uploadChunkProgress: progress }); - } - const fileInfo = { - file: file, - gid: gid, - mediaType: isArchive ? -1 : mediaType, - section: - this._section && !this._chosenSection - ? this._section - : this._chosenSection, - isImage: isImage, - isArchive: isArchive, - progressCallback: progressCallback.bind(this), - abortController: this._abortController, - attributes: attributes ? attributes : {}, - }; - // console.log("File is OK", fileInfo); - return fileInfo; - } + if (fileOk) { + function progressCallback(progress) { + this._store.setState({ uploadChunkProgress: progress }); + } + const fileInfo = { + file: file, + gid: gid, + mediaType: isArchive ? -1 : mediaType, + section: + this._section && !this._chosenSection + ? this._section + : this._chosenSection, + isImage: isImage, + isArchive: isArchive, + progressCallback: progressCallback.bind(this), + abortController: this._abortController, + attributes: attributes ? attributes : {}, + }; + // console.log("File is OK", fileInfo); + return fileInfo; + } - this._store.setState({ - uploadError: `${file.name} is not a valid file type for this project!`, - }); - return false; - } + this._store.setState({ + uploadError: `${file.name} is not a valid file type for this project!`, + }); + return false; + } - // Iterates through items from drag and drop or file select - // event and sends them to the upload worker. - async _fileSelectCallback(ev) { - // Prevent browser default behavior. - ev.preventDefault(); + // Iterates through items from drag and drop or file select + // event and sends them to the upload worker. + async _fileSelectCallback(ev) { + // Prevent browser default behavior. + ev.preventDefault(); - // Send immediate notification of adding files. - this.dispatchEvent(new Event("addingfiles", { composed: true })); + // Send immediate notification of adding files. + this.dispatchEvent(new Event("addingfiles", { composed: true })); - // Messages to send to store. - this._uploads = []; + // Messages to send to store. + this._uploads = []; - // Set a group ID on the upload. - const gid = uuidv1(); + // Set a group ID on the upload. + const gid = uuidv1(); - // Get main page. - const page = document.getElementsByTagName("project-detail")[0]; + // Get main page. + const page = document.getElementsByTagName("project-detail")[0]; - // Show loading gif. - const loading = document.createElement("img"); - loading.setAttribute("class", "loading"); - loading.setAttribute( - "src", - `${STATIC_PATH}/ui/src/images/tator_loading.gif` - ); - page._projects.appendChild(loading); - page.setAttribute("has-open-modal", ""); + // Show loading gif. + const loading = document.createElement("img"); + loading.setAttribute("class", "loading"); + loading.setAttribute( + "src", + `${STATIC_PATH}/ui/src/images/tator_loading.gif` + ); + page._projects.appendChild(loading); + page.setAttribute("has-open-modal", ""); - let numSkipped = 0; - let numStarted = 0; - let totalFiles = 0; - let totalSize = 0; - this._abortController = new AbortController(); - if (typeof ev.dataTransfer === "undefined") { - const files = ev.target.files; - totalFiles = files.length; - for (const file of files) { - const added = this._checkFile(file, gid); - if (added) { - this._uploads.push(added); - numStarted++; - totalSize += file.size; - } else { - numSkipped++; - } - } - } else { - const items = await getAllFileEntries(ev.dataTransfer.items); - for (const item of items) { - if (item.isFile) { - totalFiles++; - item.file((file) => { - const added = this._checkFile(file, gid); - if (added) { - this._uploads.push(added); - numStarted++; - totalSize += file.size; - } else { - numSkipped++; - } - }); - } - } - } - while (numSkipped + numStarted < totalFiles) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } + let numSkipped = 0; + let numStarted = 0; + let totalFiles = 0; + let totalSize = 0; + this._abortController = new AbortController(); + if (typeof ev.dataTransfer === "undefined") { + const files = ev.target.files; + totalFiles = files.length; + for (const file of files) { + const added = this._checkFile(file, gid); + if (added) { + this._uploads.push(added); + numStarted++; + totalSize += file.size; + } else { + numSkipped++; + } + } + } else { + const items = await getAllFileEntries(ev.dataTransfer.items); + for (const item of items) { + if (item.isFile) { + totalFiles++; + item.file((file) => { + const added = this._checkFile(file, gid); + if (added) { + this._uploads.push(added); + numStarted++; + totalSize += file.size; + } else { + numSkipped++; + } + }); + } + } + } + while (numSkipped + numStarted < totalFiles) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } - // Remove loading gif. - page._projects.removeChild(loading); - page.removeAttribute("has-open-modal", ""); + // Remove loading gif. + page._projects.removeChild(loading); + page.removeAttribute("has-open-modal", ""); - // If upload is big throw up a warning. - if (totalSize > 60000000000 || numStarted > 5000) { - const bigUpload = document.createElement("big-upload-form"); - const page = document.getElementsByTagName("project-detail")[0]; - page._projects.appendChild(bigUpload); - bigUpload.setAttribute("is-open", ""); - page.setAttribute("has-open-modal", ""); - bigUpload.addEventListener("close", (evt) => { - page.removeAttribute("has-open-modal", ""); - page._projects.removeChild(bigUpload); - }); - while (bigUpload.hasAttribute("is-open")) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - if (!bigUpload._confirm) { - page._leaveConfirmOk = false; - return; - } - } + // If upload is big throw up a warning. + if (totalSize > 60000000000 || numStarted > 5000) { + const bigUpload = document.createElement("big-upload-form"); + const page = document.getElementsByTagName("project-detail")[0]; + page._projects.appendChild(bigUpload); + bigUpload.setAttribute("is-open", ""); + page.setAttribute("has-open-modal", ""); + bigUpload.addEventListener("close", (evt) => { + page.removeAttribute("has-open-modal", ""); + page._projects.removeChild(bigUpload); + }); + while (bigUpload.hasAttribute("is-open")) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + if (!bigUpload._confirm) { + page._leaveConfirmOk = false; + return; + } + } - // If no files were started, notify the user. - if (numStarted == 0 && totalFiles > 0) { - page._notify( - "Invalid files!", - "No files were added! Make sure selected files are valid for this project.", - "error" - ); - } else if (numStarted > 0) { - this.dispatchEvent( - new CustomEvent("filesadded", { - detail: { numSkipped: numSkipped, numStarted: numStarted }, - composed: true, - }) - ); - let promise = new Promise((resolve) => resolve(true)); - this._store.setState({ - uploadTotalFiles: this._uploads.length, - uploadCancelled: false, - }); - for (const [idx, msg] of this._uploads.entries()) { - promise = promise - .then(() => { - if (this._cancel) { - throw `Upload of ${msg.file.name} cancelled!`; - } - this._store.setState({ - uploadFilesComplete: idx, - uploadChunkProgress: 0, - uploadFilename: msg.file.name, - }); - return uploadMedia(msg.mediaType, msg.file, msg); - }) - .then(() => { - this._store.setState({ - uploadFilesComplete: idx + 1, - }); - }); - } - promise.catch((error) => { - this._store.setState({ uploadError: error.message }); - }); - } - } + // If no files were started, notify the user. + if (numStarted == 0 && totalFiles > 0) { + page._notify( + "Invalid files!", + "No files were added! Make sure selected files are valid for this project.", + "error" + ); + } else if (numStarted > 0) { + this.dispatchEvent( + new CustomEvent("filesadded", { + detail: { numSkipped: numSkipped, numStarted: numStarted }, + composed: true, + }) + ); + let promise = new Promise((resolve) => resolve(true)); + this._store.setState({ + uploadTotalFiles: this._uploads.length, + uploadCancelled: false, + }); + for (const [idx, msg] of this._uploads.entries()) { + promise = promise + .then(() => { + if (this._cancel) { + throw `Upload of ${msg.file.name} cancelled!`; + } + this._store.setState({ + uploadFilesComplete: idx, + uploadChunkProgress: 0, + uploadFilename: msg.file.name, + }); + return uploadMedia(msg.mediaType, msg.file, msg); + }) + .then(() => { + this._store.setState({ + uploadFilesComplete: idx + 1, + }); + }); + } + promise.catch((error) => { + this._store.setState({ uploadError: error.message }); + }); + } + } } // Drop handler function to get all files async function getAllFileEntries(dataTransferItemList) { - let fileEntries = []; - // Use BFS to traverse entire directory/file structure - let queue = []; - // Unfortunately dataTransferItemList is not iterable i.e. no forEach - for (let i = 0; i < dataTransferItemList.length; i++) { - queue.push(dataTransferItemList[i].webkitGetAsEntry()); - } - while (queue.length > 0) { - let entry = queue.shift(); - if (entry.isFile) { - fileEntries.push(entry); - } else if (entry.isDirectory) { - let reader = entry.createReader(); - queue.push(...(await readAllDirectoryEntries(reader))); - } - } - return fileEntries; + let fileEntries = []; + // Use BFS to traverse entire directory/file structure + let queue = []; + // Unfortunately dataTransferItemList is not iterable i.e. no forEach + for (let i = 0; i < dataTransferItemList.length; i++) { + queue.push(dataTransferItemList[i].webkitGetAsEntry()); + } + while (queue.length > 0) { + let entry = queue.shift(); + if (entry.isFile) { + fileEntries.push(entry); + } else if (entry.isDirectory) { + let reader = entry.createReader(); + queue.push(...(await readAllDirectoryEntries(reader))); + } + } + return fileEntries; } // Get all the entries (files or sub-directories) in a directory by calling // readEntries until it returns empty array async function readAllDirectoryEntries(directoryReader) { - let entries = []; - let readEntries = await readEntriesPromise(directoryReader); - while (readEntries.length > 0) { - entries.push(...readEntries); - readEntries = await readEntriesPromise(directoryReader); - } - return entries; + let entries = []; + let readEntries = await readEntriesPromise(directoryReader); + while (readEntries.length > 0) { + entries.push(...readEntries); + readEntries = await readEntriesPromise(directoryReader); + } + return entries; } // Wrap readEntries in a promise to make working with readEntries easier async function readEntriesPromise(directoryReader) { - try { - return await new Promise((resolve, reject) => { - directoryReader.readEntries(resolve, reject); - }); - } catch (err) { - console.error(err); - } + try { + return await new Promise((resolve, reject) => { + directoryReader.readEntries(resolve, reject); + }); + } catch (err) { + console.error(err); + } } diff --git a/ui/src/js/project-detail/section-upload.js b/ui/src/js/project-detail/section-upload.js index 8f925b44e..fc1c8cdc4 100644 --- a/ui/src/js/project-detail/section-upload.js +++ b/ui/src/js/project-detail/section-upload.js @@ -46,13 +46,13 @@ export class SectionUpload extends UploadElement { }); this.uploadDialogInit.addEventListener("choose-files", (event) => { - this._fileInput.click(); - this._chosenSection = this.uploadDialogInit._parentFolders.getValue(); - this._chosenImageType = this.uploadDialogInit._imageType.getValue(); - this._chosenVideoType = this.uploadDialogInit._videoType.getValue(); - this._imageAttr = event.detail.attrImage; - this._videoAttr = event.detail.attrVideo; - }); + this._fileInput.click(); + this._chosenSection = this.uploadDialogInit._parentFolders.getValue(); + this._chosenImageType = this.uploadDialogInit._imageType.getValue(); + this._chosenVideoType = this.uploadDialogInit._videoType.getValue(); + this._imageAttr = event.detail.attrImage; + this._videoAttr = event.detail.attrVideo; + }); } connectedCallback() { From fc5fd3a0845dbfede78cc9c0e53da78b327f7136 Mon Sep 17 00:00:00 2001 From: Erin Butler Date: Fri, 6 Dec 2024 18:40:38 -0500 Subject: [PATCH 3/5] Fixes for section >> section name not ID --- ui/src/js/components/upload-dialog-init.js | 9 +++------ ui/src/js/components/upload-element.js | 2 +- ui/src/js/project-detail/section-upload.js | 11 ++++++++++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ui/src/js/components/upload-dialog-init.js b/ui/src/js/components/upload-dialog-init.js index 11df931a4..c2868c865 100644 --- a/ui/src/js/components/upload-dialog-init.js +++ b/ui/src/js/components/upload-dialog-init.js @@ -21,7 +21,7 @@ export class UploadDialogInit extends ModalDialog { this._parentFolders = document.createElement("enum-input"); this._parentFolders.setAttribute("class", "text-gray f2"); this._parentFolders.setAttribute("name", " Folder:"); - formGroup2.appendChild(this._parentFolders); + formGroup2.appendChild(this._parentFolders); this._mediaFormGroup = document.createElement("details"); this._mediaFormGroup.setAttribute("class", "hidden form-group"); @@ -217,8 +217,6 @@ export class UploadDialogInit extends ModalDialog { element.addEventListener("change", (evt) => { const value = Number(element.getValue()); const attrInfo = this._typeAttributeMap.get(value); - console.log("Event ", value, this._typeAttributeMap, attrInfo); - // element.clear(); this._setAttrList(attrInfo, type); }); @@ -231,7 +229,6 @@ export class UploadDialogInit extends ModalDialog { _setAttrList(attrList, type) { const list = []; - console.log("Setting attr list", attrList, type); if (attrList && attrList.length > 0) { for (let attr of attrList) { list.push({ @@ -256,8 +253,8 @@ export class UploadDialogInit extends ModalDialog { setupData() { const searchParams = new URLSearchParams(window.location.search), - selectedSection = searchParams.get("section"), - choices = this._sectionData.getFolderEnumChoices(); + selectedSection = searchParams.get("section"), + choices = this._sectionData.getFolderEnumChoices(); choices.unshift({ value: this._noParentName, label: this._noParentName }); this._parentFolders.choices = choices; diff --git a/ui/src/js/components/upload-element.js b/ui/src/js/components/upload-element.js index 4527dca22..118c92ca4 100644 --- a/ui/src/js/components/upload-element.js +++ b/ui/src/js/components/upload-element.js @@ -110,7 +110,7 @@ export class UploadElement extends TatorElement { abortController: this._abortController, attributes: attributes ? attributes : {}, }; - // console.log("File is OK", fileInfo); + console.log("File is OK", fileInfo); return fileInfo; } diff --git a/ui/src/js/project-detail/section-upload.js b/ui/src/js/project-detail/section-upload.js index fc1c8cdc4..0e5b015bc 100644 --- a/ui/src/js/project-detail/section-upload.js +++ b/ui/src/js/project-detail/section-upload.js @@ -47,7 +47,16 @@ export class SectionUpload extends UploadElement { this.uploadDialogInit.addEventListener("choose-files", (event) => { this._fileInput.click(); - this._chosenSection = this.uploadDialogInit._parentFolders.getValue(); + + const sectionId = Number( + this.uploadDialogInit._parentFolders?.getValue() + ); + this._chosenSection = + sectionId && this._sectionData?._sectionIdMap?.[sectionId]?.name + ? this._sectionData._sectionIdMap[sectionId].name + : null; + + this.section = this._chosenSection; this._chosenImageType = this.uploadDialogInit._imageType.getValue(); this._chosenVideoType = this.uploadDialogInit._videoType.getValue(); this._imageAttr = event.detail.attrImage; From a09bd260528d58f8fc8afc14c647c3339e4db6a1 Mon Sep 17 00:00:00 2001 From: Erin Butler Date: Fri, 6 Dec 2024 18:41:31 -0500 Subject: [PATCH 4/5] Prettier. --- ui/src/js/components/upload-dialog-init.js | 6 +++--- ui/src/js/project-detail/section-upload.js | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ui/src/js/components/upload-dialog-init.js b/ui/src/js/components/upload-dialog-init.js index c2868c865..ac7739736 100644 --- a/ui/src/js/components/upload-dialog-init.js +++ b/ui/src/js/components/upload-dialog-init.js @@ -21,7 +21,7 @@ export class UploadDialogInit extends ModalDialog { this._parentFolders = document.createElement("enum-input"); this._parentFolders.setAttribute("class", "text-gray f2"); this._parentFolders.setAttribute("name", " Folder:"); - formGroup2.appendChild(this._parentFolders); + formGroup2.appendChild(this._parentFolders); this._mediaFormGroup = document.createElement("details"); this._mediaFormGroup.setAttribute("class", "hidden form-group"); @@ -253,8 +253,8 @@ export class UploadDialogInit extends ModalDialog { setupData() { const searchParams = new URLSearchParams(window.location.search), - selectedSection = searchParams.get("section"), - choices = this._sectionData.getFolderEnumChoices(); + selectedSection = searchParams.get("section"), + choices = this._sectionData.getFolderEnumChoices(); choices.unshift({ value: this._noParentName, label: this._noParentName }); this._parentFolders.choices = choices; diff --git a/ui/src/js/project-detail/section-upload.js b/ui/src/js/project-detail/section-upload.js index 0e5b015bc..450c38089 100644 --- a/ui/src/js/project-detail/section-upload.js +++ b/ui/src/js/project-detail/section-upload.js @@ -48,15 +48,15 @@ export class SectionUpload extends UploadElement { this.uploadDialogInit.addEventListener("choose-files", (event) => { this._fileInput.click(); - const sectionId = Number( - this.uploadDialogInit._parentFolders?.getValue() - ); - this._chosenSection = - sectionId && this._sectionData?._sectionIdMap?.[sectionId]?.name - ? this._sectionData._sectionIdMap[sectionId].name - : null; - - this.section = this._chosenSection; + const sectionId = Number( + this.uploadDialogInit._parentFolders?.getValue() + ); + this._chosenSection = + sectionId && this._sectionData?._sectionIdMap?.[sectionId]?.name + ? this._sectionData._sectionIdMap[sectionId].name + : null; + + this.section = this._chosenSection; this._chosenImageType = this.uploadDialogInit._imageType.getValue(); this._chosenVideoType = this.uploadDialogInit._videoType.getValue(); this._imageAttr = event.detail.attrImage; From 44399804d211f657c839933154f48fc4de07cf3b Mon Sep 17 00:00:00 2001 From: Erin Butler Date: Fri, 6 Dec 2024 19:41:43 -0500 Subject: [PATCH 5/5] Bug fixes. --- ui/src/js/components/upload-dialog-init.js | 1 + ui/src/js/components/upload-element.js | 4 +++ ui/src/js/project-detail/section-upload.js | 42 +++++++++++++++------- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/ui/src/js/components/upload-dialog-init.js b/ui/src/js/components/upload-dialog-init.js index ac7739736..535511f80 100644 --- a/ui/src/js/components/upload-dialog-init.js +++ b/ui/src/js/components/upload-dialog-init.js @@ -257,6 +257,7 @@ export class UploadDialogInit extends ModalDialog { choices = this._sectionData.getFolderEnumChoices(); choices.unshift({ value: this._noParentName, label: this._noParentName }); + this._parentFolders.resetChoices(); this._parentFolders.choices = choices; if (selectedSection) { diff --git a/ui/src/js/components/upload-element.js b/ui/src/js/components/upload-element.js index 118c92ca4..f1a8d0d1b 100644 --- a/ui/src/js/components/upload-element.js +++ b/ui/src/js/components/upload-element.js @@ -56,12 +56,16 @@ export class UploadElement extends TatorElement { this._chosenImageType?.file_format === null ) { mediaType = this._chosenImageType; + fileOk = true; + attributes = this._imageAttr; } else if ( isVideo && this._chosenVideoType !== null && this._chosenVideoType?.file_format === null ) { mediaType = this._chosenVideoType; + fileOk = true; + attributes = this._videoAttr; } const mediaTypes = this._store.getState().mediaTypes; diff --git a/ui/src/js/project-detail/section-upload.js b/ui/src/js/project-detail/section-upload.js index 450c38089..0afe0948d 100644 --- a/ui/src/js/project-detail/section-upload.js +++ b/ui/src/js/project-detail/section-upload.js @@ -45,27 +45,43 @@ export class SectionUpload extends UploadElement { this.uploadDialogInit.open(); }); - this.uploadDialogInit.addEventListener("choose-files", (event) => { - this._fileInput.click(); + this.uploadDialogInit.addEventListener( + "choose-files", + this._handleUpdateVars.bind(this) + ); + } + + connectedCallback() { + this.init(store); + } - const sectionId = Number( - this.uploadDialogInit._parentFolders?.getValue() - ); + _handleUpdateVars(event) { + this._fileInput.click(); + + const sectionId = Number(this.uploadDialogInit._parentFolders?.getValue()); + if (sectionId !== this.uploadDialogInit._noParentName) { this._chosenSection = sectionId && this._sectionData?._sectionIdMap?.[sectionId]?.name ? this._sectionData._sectionIdMap[sectionId].name : null; this.section = this._chosenSection; - this._chosenImageType = this.uploadDialogInit._imageType.getValue(); - this._chosenVideoType = this.uploadDialogInit._videoType.getValue(); - this._imageAttr = event.detail.attrImage; - this._videoAttr = event.detail.attrVideo; - }); - } + } - connectedCallback() { - this.init(store); + const imageType = this.uploadDialogInit._imageType.getValue(); + this._chosenImageType = this._mediaTypes.find( + (type) => type.id === Number(imageType) + ); + const videoType = this.uploadDialogInit._videoType.getValue(); + this._chosenVideoType = this._mediaTypes.find( + (type) => type.id === Number(videoType) + ); + + console.log("this._chosenImageType", this._chosenImageType); + console.log("this._chosenVideoType", this._chosenVideoType); + + this._imageAttr = event.detail.attrImage; + this._videoAttr = event.detail.attrVideo; } set sectionData(val) {