Rename js directory to webapp

This commit is contained in:
Kaspar Emanuel 2021-08-17 16:14:23 +01:00
parent 375bcf4e73
commit b2eb1e0f05
75 changed files with 5 additions and 5 deletions

View file

@ -0,0 +1,389 @@
<template>
<div
class="capture-card uk-card uk-card-default uk-padding-remove uk-width-medium"
:class="{ 'uk-card-secondary': $store.state.darkMode }"
>
<div class="uk-card-media-top">
<a class="lightbox-link" :href="imgURL" :data-caption="name">
<img
class="uk-width-1-1"
:data-src="thumbURL"
:alt="id"
width="300"
height="225"
uk-img
/>
</a>
</div>
<div class="uk-card-body uk-padding-small">
<div
class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right"
uk-grid
>
<div class="uk-margin-remove-top uk-padding-remove uk-width-expand">
{{ name }}
</div>
<div class="uk-margin-remove-top uk-padding-remove uk-width-auto">
<a href="#" class="uk-icon" @click="delCaptureConfirm()">
<i class="material-icons">delete</i>
</a>
</div>
</div>
<div
class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"
>
<time>{{ time }}</time>
</div>
<div
class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-auto"
>
<a :href="metadataModalTarget" uk-toggle>More...</a>
</div>
</div>
<div class="uk-card-footer uk-padding-small">
<button
v-if="openInImjoyMenuItems.length != 0"
class="uk-icon"
type="button"
>
<img
style="width:25px;"
src="https://imjoy.io/static/img/imjoy-icon.svg"
/>
</button>
<div uk-dropdown="pos: top-center">
<ul class="uk-nav uk-dropdown-nav">
<li v-for="item in openInImjoyMenuItems" :key="item.name">
<a href="#" @click="item.callback(name, imgURL)"
><i class="material-icons">launch</i>{{ item.title }}</a
>
</li>
</ul>
</div>
&nbsp;
<div v-for="tag in tags" :key="tag" class="uk-display-inline">
<span
v-if="tag === 'temporary'"
class="uk-label uk-label-danger uk-margin-small-right"
uk-tooltip="title: Capture will be removed automatically; delay: 500"
>Temporary</span
>
<span
v-else
class="uk-label uk-margin-small-right deletable-label"
@click="delTagConfirm(tag)"
>
{{ tag }}
</span>
</div>
<a :href="tagModalTarget" uk-toggle>
<span class="uk-label uk-label-success uk-margin-small-right">Add</span>
</a>
</div>
<!-- Metadata modal -->
<div :id="metadataModalID" uk-modal>
<div
class="uk-modal-dialog uk-modal-body"
:class="{
'uk-light uk-background-secondary': $store.state.darkMode
}"
>
<button class="uk-modal-close-default" type="button" uk-close></button>
<h2 class="uk-modal-title">{{ name }}</h2>
<p><b>Path: </b>{{ path }}</p>
<p><b>Time: </b>{{ time }}</p>
<p><b>ID: </b>{{ id }}</p>
<p><b>Format: </b>{{ format }}</p>
<hr />
<div v-for="(value, key) in annotations" :key="key">
<p>
<b>{{ key }}: </b>{{ value }}
</p>
</div>
<hr />
<div class="uk-flex-bottom" uk-grid>
<div class="uk-width-2-3">
<keyvalList v-model="newAnnotations" />
</div>
<div class="uk-width-1-3">
<button
class="uk-button uk-button-primary uk-form-small uk-width-1-1"
@click="handleAnnotationsSubmit()"
>
Add annotation
</button>
</div>
</div>
</div>
</div>
<!-- New tag modal -->
<div :id="tagModalID" uk-modal>
<form
class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical"
:class="{
'uk-light uk-background-secondary': $store.state.darkMode
}"
@submit.prevent="handleTagSubmit"
>
<div class="uk-inline">
<span class="uk-form-icon"><i class="material-icons">label</i></span>
<input
v-model="newTag"
autofocus
class="uk-input uk-form-width-medium uk-form-small"
type="text"
name="tagname"
placeholder="tag"
/>
<button
class="uk-button uk-button-default uk-margin-left uk-form-small uk-modal-close"
type="button"
>
Cancel
</button>
<button
type="submit"
class="uk-button uk-button-primary uk-margin-left uk-form-small"
>
Save
</button>
</div>
</form>
</div>
</div>
</template>
<script>
import UIkit from "uikit";
import axios from "axios";
import { mapState } from "vuex";
import keyvalList from "../../fieldComponents/keyvalList";
// Export main app
export default {
name: "CaptureCard",
components: {
keyvalList
},
props: {
id: {
type: String,
required: true
},
name: {
type: String,
required: true
},
time: {
type: String,
required: true
},
path: {
type: String,
required: true
},
format: {
type: String,
required: true
},
links: {
type: Object,
required: true
},
tags: {
type: Array,
required: false,
default: function() {
return [];
}
},
annotations: {
type: Object,
required: false,
default: function() {
return {};
}
}
},
data: function() {
return {
newTag: "",
newAnnotations: {}
};
},
computed: {
tagModalID: function() {
return this.makeModalName("tag-modal-");
},
tagModalTarget: function() {
return "#" + this.tagModalID;
},
metadataModalID: function() {
return this.makeModalName("metadata-modal-");
},
metadataModalTarget: function() {
return "#" + this.metadataModalID;
},
thumbURL: function() {
return `${this.links.download.href}?thumbnail=true`;
},
imgURL: function() {
return this.links.download.href;
},
tagsURL: function() {
return this.links.tags.href;
},
annotationsURL: function() {
return this.links.annotations.href;
},
captureURL: function() {
return this.links.self.href;
},
...mapState("imjoy", { openInImjoyMenuItems: "openImageMenu" })
},
created: function() {},
methods: {
getMetadata: function() {
// Send metadata request
axios
.get(this.captureURL)
.then(response => {
this.$emit("update:tags", response.data.metadata.image.tags);
this.$emit(
"update:annotations",
response.data.metadata.image.annotations
);
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
handleTagSubmit: function(event) {
if (this.newTag !== "") {
this.newTagRequest(this.newTag);
this.newTag = "";
}
UIkit.modal(event.target.parentNode).hide();
},
handleAnnotationsSubmit: function() {
this.putAnnotationsRequest(this.newAnnotations);
this.newAnnotations = {};
},
delCaptureConfirm: function() {
var context = this;
this.modalConfirm("Permanantly delete capture?").then(function() {
context.delCaptureRequest();
});
},
delCaptureRequest: function() {
// Send tag DELETE request
axios
.delete(this.captureURL)
.then(() => {
// Emit signal to update capture list
this.$root.$emit("globalUpdateCaptures");
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
newTagRequest: function(tagString) {
// Send tag PUT request
axios
.put(this.tagsURL, [tagString])
.then(() => {
// Update tag array
this.getTagRequest();
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
putAnnotationsRequest: function(annotationsObject) {
// Send metadata PUT request
axios
.put(this.annotationsURL, annotationsObject)
.then(() => {
// Update metadata object
this.getAnnotationsRequest();
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
delTagConfirm: function(tagString) {
var context = this;
this.modalConfirm(`Remove tag '${tagString}'?`).then(function() {
context.delTagRequest(tagString);
});
},
delTagRequest: function(tagString) {
// Send tag DELETE request
axios
.delete(this.tagsURL, { data: [tagString] })
.then(() => {
// Update tag array
this.getTagRequest();
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
getTagRequest: function() {
// Send tag request
axios
.get(this.tagsURL)
.then(response => {
// Pass the update tag array to the parent
this.$emit("update:tags", response.data);
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
getAnnotationsRequest: function() {
// Send tag request
axios
.get(this.annotationsURL)
.then(response => {
// Pass the update annotation array to the parent
this.$emit("update:annotations", response.data);
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
},
makeModalName: function(prefix) {
return prefix + this.id;
}
}
};
</script>

View file

@ -0,0 +1,153 @@
<template>
<div
class="capture-card uk-card uk-card-primary uk-padding-remove uk-width-medium"
>
<div class="uk-card-media-top">
<a href="#">
<img
class="uk-width-1-1"
:data-src="thumbnail"
:alt="id"
width="300"
height="225"
uk-img
@click="onClick"
/>
</a>
</div>
<div class="uk-card-body uk-padding-small">
<div
class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right"
uk-grid
>
<div class="uk-margin-remove-top uk-padding-remove uk-width-expand">
<b>{{ type }}: </b>
{{ name }}
</div>
<div class="uk-margin-remove-top uk-padding-remove uk-width-auto">
<a href="#" class="uk-icon" @click="delAllConfirm()">
<i class="material-icons">delete</i>
</a>
</div>
</div>
<div
class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"
>
<time>{{ time }}</time>
</div>
</div>
<div class="uk-card-footer uk-padding-small">
<button
v-if="openInImjoyMenuItems.length != 0"
class="uk-icon"
type="button"
>
<img
style="width:25px;"
src="https://imjoy.io/static/img/imjoy-icon.svg"
/>
</button>
<div uk-dropdown="pos: top-center">
<ul class="uk-nav uk-dropdown-nav">
<li v-for="item in openInImjoyMenuItems" :key="item.name">
<a
href="#"
style="color:black"
@click="item.callback(name, allURLs)"
><i class="material-icons">launch</i>{{ item.title }}</a
>
</li>
</ul>
</div>
&nbsp;
<span
v-for="tag in tags"
:key="tag"
class="uk-label uk-margin-small-right deletable-label"
>
{{ tag }}
</span>
</div>
</div>
</template>
<script>
import axios from "axios";
import { mapState } from "vuex";
// Export main app
export default {
name: "ScanCard",
props: {
id: {
type: String,
required: true
},
name: {
type: String,
required: true
},
time: {
type: String,
required: true
},
type: {
type: String,
required: false,
default: "Dataset"
},
thumbnail: {
type: String,
required: true
},
captures: {
type: Array,
required: true
},
tags: {
type: Array,
required: false,
default: function() {
return [];
}
}
},
computed: {
allURLs: function() {
var urls = [];
for (var capture of this.captures) {
urls.push(capture.links.self.href);
}
return urls;
},
...mapState("imjoy", { openInImjoyMenuItems: "openScanMenu" })
},
methods: {
onClick: function() {
this.$emit("selectFolder", this.id);
},
delAllConfirm: function() {
var context = this;
this.modalConfirm(
"Permanantly delete all captures in this dataset?"
).then(function() {
context.deleteAll();
});
},
deleteAll: function() {
axios.all(this.allURLs.map(l => axios.delete(l))).then(() => {
// Emit signal to update capture list
this.$root.$emit("globalUpdateCaptures");
});
}
}
};
</script>

View file

@ -0,0 +1,169 @@
<template>
<div v-if="zipBuilderUri" class="uk-width-small">
<div v-show="downloadReady">
<button
:disabled="isDownloading"
type="button"
class="uk-button uk-button-default uk-width-1-1"
@click="downloadWithAxios()"
>
{{ isDownloading ? `${downloadProgress}%` : "Download" }}
</button>
</div>
<div v-show="!downloadReady">
<taskSubmitter
v-if="zipBuilderUri"
:can-terminate="false"
:submit-url="zipBuilderUri"
:submit-label="'Create ZIP'"
:submit-data="captureIds"
@response="onResponse"
@error="modalError"
>
</taskSubmitter>
</div>
</div>
</template>
<script>
import axios from "axios";
import taskSubmitter from "../../genericComponents/taskSubmitter";
// Export main app
export default {
name: "ZipDownloader",
components: {
taskSubmitter
},
props: {
captureIds: {
type: Array,
required: true
}
},
data: function() {
return {
downloadProgress: 0,
isDownloading: false,
downloadReady: false,
downloadUrl: null,
zipBuilderUri: null,
zipGetterUri: null,
lastSessionId: null
};
},
computed: {
pluginsUri: function() {
return `${this.$store.getters.baseUri}/api/v2/extensions`;
}
},
mounted: function() {
this.updateZipperUri();
},
created: function() {
// Watch for host 'ready', then update status
this.unwatchStoreFunction = this.$store.watch(
(state, getters) => {
return getters.ready;
},
ready => {
if (ready) {
// If the connection is now ready, update zipper URL
this.updateZipperUri();
}
}
);
},
methods: {
updateZipperUri: function() {
if (this.$store.state.available) {
axios
.get(this.pluginsUri) // Get a list of plugins
.then(response => {
var plugins = response.data;
var foundExtension = plugins.find(
e => e.title === "org.openflexure.zipbuilder"
);
// if ZipBuilderPlugin is enabled
if (foundExtension) {
// Get plugin action links
this.zipBuilderUri = foundExtension.links.build.href;
this.zipGetterUri = foundExtension.links.get.href;
}
})
.catch(error => {
this.modalError(error); // Let mixin handle error
});
} else {
this.zipBuilderUri = null;
this.zipGetterUri = null;
}
},
resetZipper: function() {
this.downloadReady = false;
},
forceFileDownload(response) {
// Start file download by creating and clicking an invisible link
// One day I hope for a better solution to exist for this...
// A man can dream.....
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", `${this.lastSessionId}.zip`); //or any other extension
document.body.appendChild(link);
link.click();
},
downloadWithAxios() {
// Configure progress indicator and response type
let config = {
responseType: "blob",
onDownloadProgress: progressEvent => {
this.downloadProgress = Math.floor(
(progressEvent.loaded * 100) / progressEvent.total
);
}
};
// Set downloading flag
this.isDownloading = true;
// Start download
axios
.get(this.downloadUrl, config)
.then(response => {
this.forceFileDownload(response);
this.deleteLastZip();
this.resetZipper();
})
.catch(error => {
this.modalError(error); // Let mixin handle error
})
.finally(() => {
this.isDownloading = false;
});
},
deleteLastZip() {
axios.delete(this.downloadUrl).catch(error => {
this.modalError(error); // Let mixin handle error
});
},
onResponse: function(response) {
this.lastSessionId = response.output.id;
this.downloadUrl = `${this.zipGetterUri}/${this.lastSessionId}`;
this.downloadReady = true;
}
}
};
</script>