vue-girder-slicer-cli-ui 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/dist/api/girderRest.d.ts +36 -0
- package/dist/api/girderSlicerApi.d.ts +32 -0
- package/dist/components/FileBrowser/DataBrowser.tw.vue.d.ts +177 -0
- package/dist/components/FileBrowser/Modal.tw.vue.d.ts +27 -0
- package/dist/components/FileBrowser/RootSelection.tw.vue.d.ts +41 -0
- package/dist/components/FileBrowser/utils.d.ts +9 -0
- package/dist/components/GirderControlsPanel.tw.vue.d.ts +29 -0
- package/dist/components/GirderSlicerParameter.tw.vue.d.ts +50 -0
- package/dist/components/GirderSlicerTaskButton.tw.vue.d.ts +42 -0
- package/dist/components/GirderSlicerTaskCard.tw.vue.d.ts +44 -0
- package/dist/components/GirderSlicerTaskMenu.tw.vue.d.ts +49 -0
- package/dist/components/GlicerSlicerTaskMenuModal.tw.vue.d.ts +34 -0
- package/dist/components/GlicerSlicerTaskMenuModalButton.tw.vue.d.ts +38 -0
- package/dist/components/GriderSlicerTasksIntegrated.tw.vue.d.ts +74 -0
- package/dist/components/Parameters/BaseParameter.tw.vue.d.ts +21 -0
- package/dist/components/Parameters/BooleanParameter.tw.vue.d.ts +18 -0
- package/dist/components/Parameters/ColorParameter.tw.vue.d.ts +19 -0
- package/dist/components/Parameters/EnumerationParameters.tw.vue.d.ts +19 -0
- package/dist/components/Parameters/FileParameter.tw.vue.d.ts +32 -0
- package/dist/components/Parameters/RangeParameter.tw.vue.d.ts +19 -0
- package/dist/favicon.ico +0 -0
- package/dist/girderTypes.d.ts +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/parser/constraints.d.ts +4 -0
- package/dist/parser/convert.d.ts +7 -0
- package/dist/parser/defaultValue.d.ts +12 -0
- package/dist/parser/group.d.ts +9 -0
- package/dist/parser/index.d.ts +8 -0
- package/dist/parser/panel.d.ts +11 -0
- package/dist/parser/param.d.ts +6 -0
- package/dist/parser/parse.d.ts +20 -0
- package/dist/parser/parserTypes.d.ts +58 -0
- package/dist/parser/widget.d.ts +6 -0
- package/dist/style.css +1 -0
- package/dist/tailwind.css +1370 -0
- package/dist/utils.d.ts +5 -0
- package/dist/vue-girder-slicer-cli-ui.js +12987 -0
- package/dist/vue-girder-slicer-cli-ui.umd.cjs +59 -0
- package/package.json +135 -0
- package/src/components/FileBrowser/DataBrowser.tw.vue +842 -0
- package/src/components/FileBrowser/Modal.tw.vue +82 -0
- package/src/components/FileBrowser/RootSelection.tw.vue +83 -0
- package/src/components/FileBrowser/utils.ts +48 -0
- package/src/components/GirderControlsPanel.tw.vue +94 -0
- package/src/components/GirderSlicerParameter.tw.vue +84 -0
- package/src/components/GirderSlicerTaskButton.tw.vue +208 -0
- package/src/components/GirderSlicerTaskCard.tw.vue +184 -0
- package/src/components/GirderSlicerTaskMenu.tw.vue +217 -0
- package/src/components/GlicerSlicerTaskMenuModal.tw.vue +77 -0
- package/src/components/GlicerSlicerTaskMenuModalButton.tw.vue +106 -0
- package/src/components/GriderSlicerTasksIntegrated.tw.vue +73 -0
- package/src/components/Parameters/BaseParameter.tw.vue +76 -0
- package/src/components/Parameters/BooleanParameter.tw.vue +54 -0
- package/src/components/Parameters/ColorParameter.tw.vue +58 -0
- package/src/components/Parameters/EnumerationParameters.tw.vue +66 -0
- package/src/components/Parameters/FileParameter.tw.vue +172 -0
- package/src/components/Parameters/RangeParameter.tw.vue +74 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import {defineComponent} from 'vue';
|
|
3
|
+
import { mdiClose } from '@mdi/js';
|
|
4
|
+
import SvgIcon from '@jamescoyle/vue-icon';
|
|
5
|
+
|
|
6
|
+
export default defineComponent({
|
|
7
|
+
components: {
|
|
8
|
+
SvgIcon
|
|
9
|
+
},
|
|
10
|
+
props: {
|
|
11
|
+
isVisible: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
},
|
|
15
|
+
disabledConfirm: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: true,
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
setup(_, { emit}) {
|
|
21
|
+
const cancel = () => {
|
|
22
|
+
emit('cancel')
|
|
23
|
+
}
|
|
24
|
+
const confirm = () => {
|
|
25
|
+
emit('confirm')
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
cancel,
|
|
29
|
+
confirm,
|
|
30
|
+
mdiClose,
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<div v-if="isVisible">
|
|
39
|
+
<div class="fixed inset-0 z-50 flex justify-center items-center ">
|
|
40
|
+
<div class="gsu-card flex flex-col max-w-5xl rounded-lg shadow-lg">
|
|
41
|
+
<!-- Header -->
|
|
42
|
+
<div class="p-5">
|
|
43
|
+
<div class="grid grid-cols-12">
|
|
44
|
+
<span class="col-span-10 text-xl my-2 ml-5">
|
|
45
|
+
<slot name="header">
|
|
46
|
+
<h3 class="text-2xl font-semibold">Modal Header</h3>
|
|
47
|
+
</slot>
|
|
48
|
+
</span>
|
|
49
|
+
<span class="col-start-12 col-span-1 justify-self-end mr-5 my-2 " @click="$emit('cancel')">
|
|
50
|
+
<svg-icon
|
|
51
|
+
type="mdi"
|
|
52
|
+
:path="mdiClose"
|
|
53
|
+
:size="30"
|
|
54
|
+
class="gsu-icon gsu-clickable"
|
|
55
|
+
data-dismiss="modal"
|
|
56
|
+
aria-label="Close"
|
|
57
|
+
>
|
|
58
|
+
<span aria-hidden="true">×</span>
|
|
59
|
+
</svg-icon>
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<slot name="body">
|
|
65
|
+
<div class="p-6">
|
|
66
|
+
<p>This is a modal body content. Let's make this line a bit longer to see the width.</p>
|
|
67
|
+
</div>
|
|
68
|
+
</slot>
|
|
69
|
+
|
|
70
|
+
<div class="p-6 flex justify-end items-center">
|
|
71
|
+
<button class="bg-red-500 border-solid border-2 border-borderColor font-bold py-2 px-4 rounded" @click="cancel">Cancel</button>
|
|
72
|
+
<button
|
|
73
|
+
class=" btn ml-2 font-bold py-2 px-4 rounded"
|
|
74
|
+
:class="{ 'gsu-disabled-button': disabledConfirm, 'gsu-btn-accept': !disabledConfirm }"
|
|
75
|
+
@click="!disabledConfirm && confirm()"
|
|
76
|
+
>Confirm</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="opacity-25 fixed inset-0 z-40 bg-black"></div>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PropType, computed, defineComponent } from 'vue'
|
|
3
|
+
import { GirderModel, GirderModelType} from '../../girderTypes';
|
|
4
|
+
export default defineComponent({
|
|
5
|
+
props: {
|
|
6
|
+
value: {
|
|
7
|
+
type: String,
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
home: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: true,
|
|
13
|
+
},
|
|
14
|
+
users: {
|
|
15
|
+
type: Array as PropType<GirderModel[]>,
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
collections: {
|
|
19
|
+
type: Array as PropType<GirderModel[]>,
|
|
20
|
+
required: true,
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
setup(props, { emit }) {
|
|
24
|
+
const filteredUsers = computed(() => props.users?.filter((item) => item._id !== props.home));
|
|
25
|
+
const update = (e: Event) => {
|
|
26
|
+
let type: GirderModelType = 'user';
|
|
27
|
+
const val = (e.target as HTMLSelectElement).value;
|
|
28
|
+
let name = '';
|
|
29
|
+
if (props.collections && props.users) {
|
|
30
|
+
const findType = props.collections.concat(props.users)
|
|
31
|
+
const found = findType.find((item) => item._id === val);
|
|
32
|
+
if (found) {
|
|
33
|
+
type = found._modelType;
|
|
34
|
+
if (type === 'user') {
|
|
35
|
+
name = `${found.firstName} ${found.lastName}`
|
|
36
|
+
} else {
|
|
37
|
+
name = found.name;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
emit('change', {val, type, name});
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
filteredUsers,
|
|
45
|
+
update,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
</script>
|
|
50
|
+
<template>
|
|
51
|
+
<select
|
|
52
|
+
:value="value"
|
|
53
|
+
class="root-select bg-backgroundColor border-solid border-2 border-bgMutedColor"
|
|
54
|
+
@change="update($event)"
|
|
55
|
+
>
|
|
56
|
+
<option :value="home">
|
|
57
|
+
Home
|
|
58
|
+
</option>
|
|
59
|
+
<optgroup label="Collections">
|
|
60
|
+
<option
|
|
61
|
+
v-for="item in collections"
|
|
62
|
+
:key="item._id"
|
|
63
|
+
:value="item._id"
|
|
64
|
+
>
|
|
65
|
+
{{ item.name }}
|
|
66
|
+
</option>
|
|
67
|
+
</optgroup>
|
|
68
|
+
<optgroup label="Users">
|
|
69
|
+
<option
|
|
70
|
+
v-for="item in filteredUsers"
|
|
71
|
+
:key="item._id"
|
|
72
|
+
:value="item._id"
|
|
73
|
+
>
|
|
74
|
+
{{ item.login }}
|
|
75
|
+
</option>
|
|
76
|
+
</optgroup>
|
|
77
|
+
</select>
|
|
78
|
+
</template>
|
|
79
|
+
<style scoped>
|
|
80
|
+
.root-select {
|
|
81
|
+
height: 34px;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const sizeFormatter =(size: number, { base = 1024, unit = 'B' } = {}) =>{
|
|
2
|
+
if (size < base) {
|
|
3
|
+
return `${size} ${unit}`;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let i;
|
|
7
|
+
let val = size;
|
|
8
|
+
for (i = 0; val >= base && i < 4; i += 1) {
|
|
9
|
+
val /= base;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return `${val.toFixed(2)} ${['', 'K', 'M', 'G', 'T'][i]}${unit}`;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const countFormatter = (count: number | null) => {
|
|
16
|
+
if (count && count > 1000) {
|
|
17
|
+
return `${(count / 1000).toFixed(2)}k`;
|
|
18
|
+
}
|
|
19
|
+
return count ? count : 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const convertInputNumber = (e: Event) => {
|
|
23
|
+
const val = (e.target as HTMLInputElement).value;
|
|
24
|
+
return parseInt(val);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const convertInputString= (e: Event) => {
|
|
28
|
+
const val = (e.target as HTMLInputElement).value;
|
|
29
|
+
return val as string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isValidRegex(pattern: string) {
|
|
33
|
+
try {
|
|
34
|
+
new RegExp(pattern);
|
|
35
|
+
return true;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// The RegExp constructor throws a SyntaxError if the pattern is invalid.
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
sizeFormatter,
|
|
44
|
+
isValidRegex,
|
|
45
|
+
countFormatter,
|
|
46
|
+
convertInputNumber,
|
|
47
|
+
convertInputString,
|
|
48
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PropType, defineComponent, ref } from 'vue'
|
|
3
|
+
import { mdiChevronDown, mdiChevronUp } from '@mdi/js' ;
|
|
4
|
+
import SvgIcon from '@jamescoyle/vue-icon';
|
|
5
|
+
import type { XMLPanel, XMLParameters } from '../parser/parserTypes';
|
|
6
|
+
import GirderSlicerParameter from './GirderSlicerParameter.tw.vue';
|
|
7
|
+
export default defineComponent({
|
|
8
|
+
components: {
|
|
9
|
+
GirderSlicerParameter,
|
|
10
|
+
SvgIcon,
|
|
11
|
+
},
|
|
12
|
+
props: {
|
|
13
|
+
panel: {
|
|
14
|
+
type: Object as PropType<XMLPanel>,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
collapseOverride: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
required: false,
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
setup(props, { emit }) {
|
|
23
|
+
const collapsed = ref(!props.panel.advanced)
|
|
24
|
+
const updateParams = (e: XMLParameters[]) =>{
|
|
25
|
+
emit('change', e);
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
collapsed,
|
|
29
|
+
updateParams,
|
|
30
|
+
mdiChevronDown,
|
|
31
|
+
mdiChevronUp,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
</script>
|
|
36
|
+
<template>
|
|
37
|
+
<div class="gsu-card relative flex flex-col min-w-0 rounded break-words">
|
|
38
|
+
<div
|
|
39
|
+
v-if="panel.groups.length === 1"
|
|
40
|
+
class="mb-3 mb-0"
|
|
41
|
+
>
|
|
42
|
+
<div
|
|
43
|
+
class="flex flex-wrap justify-content-left g-0"
|
|
44
|
+
@click="collapsed = !collapsed"
|
|
45
|
+
>
|
|
46
|
+
<div class=":flex-growauto">
|
|
47
|
+
<svg-icon
|
|
48
|
+
type="mdi"
|
|
49
|
+
:path="collapsed ? mdiChevronUp : mdiChevronDown"
|
|
50
|
+
:size="30"
|
|
51
|
+
class="pb-2 icon"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div class=":flex-growauto">
|
|
55
|
+
<h5>
|
|
56
|
+
{{ panel.groups[0].label }}
|
|
57
|
+
</h5>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div
|
|
61
|
+
v-if="!collapsed"
|
|
62
|
+
class="flex flex-wrap pad-left"
|
|
63
|
+
>
|
|
64
|
+
<girder-slicer-parameter
|
|
65
|
+
:parameters="panel.groups[0].parameters"
|
|
66
|
+
@change="updateParams($event)"
|
|
67
|
+
@input-selected="$emit('input-selected', $event)"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
<style scoped>
|
|
74
|
+
.icon:hover {
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
}
|
|
77
|
+
.collapse-enter-active {
|
|
78
|
+
animation: collapsing reverse 500ms ease;
|
|
79
|
+
}
|
|
80
|
+
.collapse-leave-active {
|
|
81
|
+
animation: collapsing 500ms ease;
|
|
82
|
+
}
|
|
83
|
+
.pad-left {
|
|
84
|
+
padding-left: 15px;
|
|
85
|
+
}
|
|
86
|
+
@keyframes collapsing {
|
|
87
|
+
from {
|
|
88
|
+
max-height: 1000px;
|
|
89
|
+
}
|
|
90
|
+
to {
|
|
91
|
+
max-height: 0px;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PropType, defineComponent, onMounted, ref, } from 'vue';
|
|
3
|
+
import type { XMLParameters } from '../parser/parserTypes';
|
|
4
|
+
import BaseParameter from './Parameters/BaseParameter.tw.vue';
|
|
5
|
+
import BooleanParameter from './Parameters/BooleanParameter.tw.vue';
|
|
6
|
+
import EnumerationParameters from './Parameters/EnumerationParameters.tw.vue';
|
|
7
|
+
import FileParmaeter from './Parameters/FileParameter.tw.vue';
|
|
8
|
+
import ColorParameter from './Parameters/ColorParameter.tw.vue';
|
|
9
|
+
export default defineComponent({
|
|
10
|
+
components: {
|
|
11
|
+
BaseParameter,
|
|
12
|
+
BooleanParameter,
|
|
13
|
+
EnumerationParameters,
|
|
14
|
+
FileParmaeter,
|
|
15
|
+
ColorParameter
|
|
16
|
+
},
|
|
17
|
+
props: {
|
|
18
|
+
parameters: {
|
|
19
|
+
type: Array as PropType<XMLParameters[]>,
|
|
20
|
+
required: true
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
setup(props, { emit }) {
|
|
24
|
+
const baseParmaeters = ['string', 'number', 'vector', 'string-vector', 'number-vector']
|
|
25
|
+
const paramCopy = ref(props.parameters);
|
|
26
|
+
onMounted(() => {
|
|
27
|
+
paramCopy.value = props.parameters;
|
|
28
|
+
})
|
|
29
|
+
const updateParameter = (data: XMLParameters, index: number) => {
|
|
30
|
+
paramCopy.value.splice(index, 1, data);
|
|
31
|
+
emit('change', paramCopy.value);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
baseParmaeters,
|
|
35
|
+
paramCopy,
|
|
36
|
+
updateParameter,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
<template>
|
|
42
|
+
<span>
|
|
43
|
+
<div
|
|
44
|
+
v-for="(parameter, index) in paramCopy"
|
|
45
|
+
:key="`${parameter.title}_${index}`"
|
|
46
|
+
>
|
|
47
|
+
<div class="flex flex-wrap">
|
|
48
|
+
<div class="flex-growauto">
|
|
49
|
+
<base-parameter
|
|
50
|
+
v-if="baseParmaeters.includes(parameter.type)"
|
|
51
|
+
:data="parameter"
|
|
52
|
+
@change="updateParameter($event, index)"
|
|
53
|
+
/>
|
|
54
|
+
<boolean-parameter
|
|
55
|
+
v-else-if="parameter.slicerType === 'boolean'"
|
|
56
|
+
:data="parameter"
|
|
57
|
+
@change="updateParameter($event, index)"
|
|
58
|
+
/>
|
|
59
|
+
<enumeration-parameters
|
|
60
|
+
v-else-if="parameter.slicerType === 'number-enumeration' || parameter.slicerType === 'string-enumeration'"
|
|
61
|
+
:data="parameter"
|
|
62
|
+
@change="updateParameter($event, index)"
|
|
63
|
+
/>
|
|
64
|
+
<file-parmaeter
|
|
65
|
+
v-else-if="['file', 'item', 'directory', 'image', 'multi'].includes(parameter.slicerType)"
|
|
66
|
+
:data="parameter"
|
|
67
|
+
@change="updateParameter($event, index)"
|
|
68
|
+
@input-selected="$emit('input-selected', $event)"
|
|
69
|
+
/>
|
|
70
|
+
<color-parameter
|
|
71
|
+
v-else-if="parameter.slicerType === 'color'"
|
|
72
|
+
:data="parameter"
|
|
73
|
+
@change="updateParameter($event, index)"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</span>
|
|
79
|
+
</template>
|
|
80
|
+
<style scoped>
|
|
81
|
+
a {
|
|
82
|
+
color: #42b983;
|
|
83
|
+
}
|
|
84
|
+
</style>
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Ref, computed, defineComponent, onMounted, ref } from "vue";
|
|
3
|
+
import { extractImageInfo } from "../utils";
|
|
4
|
+
import RestClient from "../api/girderRest";
|
|
5
|
+
import { useGirderSlicerApi } from "../api/girderSlicerApi";
|
|
6
|
+
import { mdiAlert, mdiChevronDown, mdiChevronRight } from "@mdi/js";
|
|
7
|
+
import SvgIcon from "@jamescoyle/vue-icon";
|
|
8
|
+
import { OnClickOutside } from '@vueuse/components'
|
|
9
|
+
|
|
10
|
+
type TaskHierarchy = Record<
|
|
11
|
+
string,
|
|
12
|
+
{
|
|
13
|
+
tag: string;
|
|
14
|
+
tasks: {
|
|
15
|
+
imageBase: string;
|
|
16
|
+
imageTag: string;
|
|
17
|
+
_id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
}[];
|
|
21
|
+
}[]
|
|
22
|
+
>;
|
|
23
|
+
export default defineComponent({
|
|
24
|
+
components: {
|
|
25
|
+
SvgIcon,
|
|
26
|
+
OnClickOutside,
|
|
27
|
+
},
|
|
28
|
+
props: {
|
|
29
|
+
apiUrl: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: "api/v1",
|
|
32
|
+
},
|
|
33
|
+
filter: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: "",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
setup(props, { emit }) {
|
|
39
|
+
const girderRest = new RestClient({ apiRoot: props.apiUrl });
|
|
40
|
+
const loggedIn = computed(() => girderRest?.token);
|
|
41
|
+
const slicerApi = useGirderSlicerApi(girderRest);
|
|
42
|
+
const results: Ref<TaskHierarchy> = ref({});
|
|
43
|
+
const clicked = ref(false);
|
|
44
|
+
const getData = async () => {
|
|
45
|
+
try {
|
|
46
|
+
const response = await slicerApi.getSlicerList();
|
|
47
|
+
// ground items by their image
|
|
48
|
+
const taskHierarchy: TaskHierarchy = {};
|
|
49
|
+
response.data.forEach((task) => {
|
|
50
|
+
// Lets get the base image name
|
|
51
|
+
const imageInfo = extractImageInfo(task.image);
|
|
52
|
+
if (imageInfo) {
|
|
53
|
+
// We create a root for it
|
|
54
|
+
if (taskHierarchy[imageInfo.baseName] === undefined) {
|
|
55
|
+
taskHierarchy[imageInfo.baseName] = [
|
|
56
|
+
{ tag: imageInfo.tag, tasks: [] },
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
const foundIndex = taskHierarchy[imageInfo.baseName].findIndex(
|
|
60
|
+
(item) => item.tag === imageInfo.tag
|
|
61
|
+
);
|
|
62
|
+
if (foundIndex !== -1) {
|
|
63
|
+
taskHierarchy[imageInfo.baseName][foundIndex].tasks.push({
|
|
64
|
+
imageBase: imageInfo.baseName,
|
|
65
|
+
imageTag: imageInfo.tag,
|
|
66
|
+
_id: task._id,
|
|
67
|
+
name: task.name,
|
|
68
|
+
description: task.description,
|
|
69
|
+
});
|
|
70
|
+
} else if (foundIndex === -1) {
|
|
71
|
+
taskHierarchy[imageInfo.baseName].push({
|
|
72
|
+
tag: imageInfo.tag,
|
|
73
|
+
tasks: [
|
|
74
|
+
{
|
|
75
|
+
imageBase: imageInfo.baseName,
|
|
76
|
+
imageTag: imageInfo.tag,
|
|
77
|
+
_id: task._id,
|
|
78
|
+
name: task.name,
|
|
79
|
+
description: task.description,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
results.value = taskHierarchy;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error("Cannot communicate with server");
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const select = (id: string) => {
|
|
92
|
+
emit("selected", id);
|
|
93
|
+
};
|
|
94
|
+
onMounted(() => {
|
|
95
|
+
getData();
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
results,
|
|
99
|
+
mdiAlert,
|
|
100
|
+
mdiChevronDown,
|
|
101
|
+
mdiChevronRight,
|
|
102
|
+
loggedIn,
|
|
103
|
+
select,
|
|
104
|
+
clicked,
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
</script>
|
|
109
|
+
<template>
|
|
110
|
+
<div>
|
|
111
|
+
<on-click-outside @trigger="clicked = false">
|
|
112
|
+
<div v-if="loggedIn" class="dropdown inline-block relative w-56">
|
|
113
|
+
<button
|
|
114
|
+
class="bg-backgroundColor text-textColor font-semibold py-2 px-4 rounded inline-flex items-center
|
|
115
|
+
rounded-sm flex items-center min-w-32"
|
|
116
|
+
@click="clicked = true"
|
|
117
|
+
>
|
|
118
|
+
<span class="pr-1 font-semibold flex-1">Tasks</span>
|
|
119
|
+
<span>
|
|
120
|
+
<svg-icon type="mdi" :path="mdiChevronDown" :size="30" class="pb-1" />
|
|
121
|
+
|
|
122
|
+
</span>
|
|
123
|
+
</button>
|
|
124
|
+
<ul
|
|
125
|
+
v-if="clicked"
|
|
126
|
+
class="absolute text-textColor"
|
|
127
|
+
>
|
|
128
|
+
<li
|
|
129
|
+
class="dropdown w-56"
|
|
130
|
+
v-for="(item, key) in results"
|
|
131
|
+
:key="key"
|
|
132
|
+
>
|
|
133
|
+
<a class="rounded-t bg-backgroundColor hover:bg-bgMutedColor py-2 px-4 block whitespace-no-wrap" href="#">
|
|
134
|
+
<span class="pr-1 flex-1"
|
|
135
|
+
>{{ item.length === 1 ? `${key}:${item[0].tag}` : key }}
|
|
136
|
+
</span>
|
|
137
|
+
<span class="mr-auto">
|
|
138
|
+
</span>
|
|
139
|
+
</a>
|
|
140
|
+
<ul
|
|
141
|
+
v-if="item.length === 1"
|
|
142
|
+
class="dropdown-content absolute hidden text-textColor left-44 top-0"
|
|
143
|
+
>
|
|
144
|
+
<li
|
|
145
|
+
v-for="task in item[0].tasks"
|
|
146
|
+
:key="task._id"
|
|
147
|
+
class="bg-backgroundColor hover:bg-bgMutedColor py-2 px-4 block whitespace-no-wrap"
|
|
148
|
+
@click="select(task._id)"
|
|
149
|
+
>
|
|
150
|
+
{{ task.name }}
|
|
151
|
+
</li>
|
|
152
|
+
</ul>
|
|
153
|
+
<ul
|
|
154
|
+
v-else
|
|
155
|
+
class="dropdown-content absolute hidden text-textColor left-44 top-0"
|
|
156
|
+
>
|
|
157
|
+
<li
|
|
158
|
+
v-for="tag in item"
|
|
159
|
+
:key="tag.tag"
|
|
160
|
+
class="dropdown w-56"
|
|
161
|
+
>
|
|
162
|
+
<a class="bg-backgroundColor hover:bg-bgMutedColor py-2 px-4 block whitespace-no-wrap" href="#">
|
|
163
|
+
<span class="pr-1">{{ tag.tag }}</span>
|
|
164
|
+
<span class="mr-auto">
|
|
165
|
+
<svg-icon type="mdi" :path="mdiChevronRight" :size="30" class="pb-1" style="display:inline" />
|
|
166
|
+
|
|
167
|
+
</span>
|
|
168
|
+
</a>
|
|
169
|
+
<ul
|
|
170
|
+
class="dropdown-content absolute hidden text-textColor left-44 top-0"
|
|
171
|
+
>
|
|
172
|
+
<li
|
|
173
|
+
v-for="task in tag.tasks"
|
|
174
|
+
:key="task._id"
|
|
175
|
+
@click="select(task._id)"
|
|
176
|
+
class="bg-backgroundColor hover:bg-bgMutedColor py-2 px-4 block whitespace-no-wrap"
|
|
177
|
+
>
|
|
178
|
+
<a class="bg-backgroundColor hover:bg-bgMutedColor py-2 px-4 block whitespace-no-wrap" href="#">
|
|
179
|
+
{{ task.name }}
|
|
180
|
+
</a>
|
|
181
|
+
</li>
|
|
182
|
+
</ul>
|
|
183
|
+
</li>
|
|
184
|
+
</ul>
|
|
185
|
+
</li>
|
|
186
|
+
</ul>
|
|
187
|
+
</div>
|
|
188
|
+
<div v-else>
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
class="inline-block align-middle text-center select-none border font-normal whitespace-no-wrap py-2 px-4 rounded text-base leading-normal no-underline text-yellow-dark border-yellow bg-white hover:bg-yellow-light hover:text-yellow-darker"
|
|
192
|
+
data-toggle=""
|
|
193
|
+
data-placement="top"
|
|
194
|
+
title="Not Logged In"
|
|
195
|
+
>
|
|
196
|
+
Error
|
|
197
|
+
<svg-icon type="mdi" :path="mdiAlert" :size="30" class="pb-1" />
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
</on-click-outside>
|
|
201
|
+
</div>
|
|
202
|
+
</template>
|
|
203
|
+
<style scoped lang="scss">
|
|
204
|
+
.dropdown:hover > .dropdown-content {
|
|
205
|
+
display: block;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
</style>
|