Skip to content

Commit 158ceb3

Browse files
Add shared directories in the shared tab
1 parent 8f56b73 commit 158ceb3

File tree

7 files changed

+381
-42
lines changed

7 files changed

+381
-42
lines changed

client/platform/web-girder/api/dataset.service.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ async function getDatasetList(
4040
return response;
4141
}
4242

43+
async function getSharedWithMeFolders(
44+
limit?: number,
45+
offset?: number,
46+
sort?: string,
47+
sortDir?: number,
48+
) {
49+
const response = await girderRest.get<GirderModel[]>('dive_dataset/shared-folders', {
50+
params: {
51+
limit,
52+
offset,
53+
sort,
54+
sortDir,
55+
},
56+
});
57+
response.data.forEach((element) => {
58+
// eslint-disable-next-line no-param-reassign
59+
element._modelType = 'folder';
60+
});
61+
return response;
62+
}
63+
4364
interface MediaResource extends FrameImage {
4465
id: string;
4566
}
@@ -151,6 +172,7 @@ export {
151172
clone,
152173
getDataset,
153174
getDatasetList,
175+
getSharedWithMeFolders,
154176
getDatasetMedia,
155177
importAnnotationFile,
156178
makeViameFolder,
Lines changed: 163 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
11
<script lang="ts">
22
import {
33
defineComponent, ref, reactive, watch, toRefs,
4+
Ref,
5+
computed,
46
} from 'vue';
57
import type { DataOptions } from 'vuetify';
6-
import { GirderModel, mixins } from '@girder/components/src';
8+
import {
9+
GirderModel,
10+
mixins,
11+
GirderDataBrowser,
12+
GirderDataTable,
13+
} from '@girder/components/src';
714
import { clientSettings } from 'dive-common/store/settings';
815
import { itemsPerPageOptions } from 'dive-common/constants';
9-
import { getDatasetList } from '../api';
10-
import { useStore, LocationType } from '../store/types';
16+
import { getSharedWithMeFolders } from '../api';
17+
import {
18+
useStore, LocationState, RootlessLocationType,
19+
} from '../store/types';
20+
import DataSharedBreadCrumb from './DataSharedBreadCrumb.vue';
1121
1222
export default defineComponent({
1323
name: 'DataShared',
24+
components: {
25+
GirderDataBrowser,
26+
GirderDataTable,
27+
DataSharedBreadCrumb,
28+
},
1429
setup() {
15-
const total = ref();
30+
const total = ref(0);
31+
const path: Ref<RootlessLocationType[]> = ref([]);
32+
1633
const dataList = ref([] as GirderModel[]);
1734
const tableOptions = reactive({
1835
page: 1,
@@ -21,14 +38,8 @@ export default defineComponent({
2138
} as DataOptions);
2239
const store = useStore();
2340
const { getters } = store;
24-
const locationStore = store.state.Location;
25-
26-
const headers = [
27-
{ text: 'File Name', value: 'name' },
28-
{ text: '', value: 'annotator', sortable: false },
29-
{ text: 'File Size', value: 'formattedSize' },
30-
{ text: 'Shared By', value: 'ownerLogin' },
31-
];
41+
const locationStore: LocationState = store.state.Location;
42+
const localLocation = ref();
3243
3344
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3445
const fixSize: any = mixins.sizeFormatter.methods;
@@ -41,8 +52,8 @@ export default defineComponent({
4152
const offset = (page - 1) * clientSettings.rowsPerPage;
4253
const sort = sortBy[0] || 'created';
4354
const sortDir = sortDesc[0] === false ? 1 : -1;
44-
const shared = true;
45-
const response = await getDatasetList(limit, offset, sort, sortDir, shared);
55+
56+
const response = await getSharedWithMeFolders(limit, offset, sort, sortDir);
4657
dataList.value = response.data;
4758
total.value = Number.parseInt(response.headers['girder-total-count'], 10);
4859
dataList.value.forEach((element) => {
@@ -51,14 +62,36 @@ export default defineComponent({
5162
});
5263
};
5364
54-
function setLocation(location: LocationType) {
55-
store.dispatch('Location/setRouteFromLocation', location);
65+
function setLocation(location: RootlessLocationType) {
66+
localLocation.value = location;
67+
for (let i = 0; i < path.value.length; i += 1) {
68+
if (path.value[i]._id === location._id) {
69+
path.value = path.value.slice(0, i + 1);
70+
return;
71+
}
72+
}
73+
path.value.push(location);
74+
}
75+
76+
function resetLocation() {
77+
path.value = [];
5678
}
5779
5880
function isAnnotationFolder(item: GirderModel) {
5981
return item._modelType === 'folder' && item.meta.annotate;
6082
}
6183
84+
const rows = computed(() => dataList.value.map((item) => ({
85+
...item,
86+
humanSize: fixSize.formatSize(item.size),
87+
icon: item.public ? 'folder' : 'folderNonPublic',
88+
})));
89+
90+
const options = ref({
91+
itemsPerPage: clientSettings.rowsPerPage,
92+
page: 1,
93+
});
94+
6295
watch(tableOptions, updateOptions, {
6396
deep: true,
6497
});
@@ -67,42 +100,124 @@ export default defineComponent({
67100
updateOptions();
68101
return {
69102
isAnnotationFolder,
70-
dataList,
71103
getters,
72-
updateOptions,
73104
setLocation,
105+
resetLocation,
106+
rows,
74107
total,
75108
locationStore,
76109
clientSettings,
77110
itemsPerPageOptions,
111+
options,
112+
notSharedLocation: localLocation,
78113
...toRefs(tableOptions),
79-
headers,
114+
path,
80115
};
81116
},
82117
});
83118
84119
</script>
85120

86121
<template>
87-
<v-data-table
122+
<v-card v-if="rows.length === 0">
123+
<div class="no-shared">
124+
<span class="pr-4">No datasets have been shared with you yet.</span>
125+
<a href="https://kitware.github.io/dive/Web-Version/#sharing-data-with-teams">Learn more about sharing</a>
126+
</div>
127+
</v-card>
128+
<girder-data-table
129+
v-else-if="path.length === 0"
130+
@rowclick="setLocation"
88131
v-model="locationStore.selected"
89132
:selectable="!getters['Location/locationIsViameFolder']"
90-
:location="locationStore.location"
91-
:headers="headers"
92-
:page.sync="page"
93-
:items-per-page.sync="clientSettings.rowsPerPage"
94-
:sort-by.sync="sortBy"
95-
:sort-desc.sync="sortDesc"
96133
:server-items-length="total"
97-
:items="dataList"
98-
:footer-props="{ itemsPerPageOptions }"
134+
:items-per-page-options="itemsPerPageOptions"
135+
:options="options"
136+
:loading="false"
99137
item-key="_id"
100138
show-select
101-
@input="$emit('input', $event)"
102-
@update:location="setLocation"
139+
:rows="rows">
140+
<template #header="{ props, on }">
141+
<thead>
142+
<tr
143+
:class="$vuetify.theme.dark ? 'darken-2' : 'lighten-5'"
144+
class="secondary"
145+
>
146+
<th class="pl-3 pr-0" width="1%">
147+
<v-checkbox
148+
:input-value="props.everyItem"
149+
class="pr-2"
150+
color="accent"
151+
hide-details="hide-details"
152+
@click.native="on['toggle-select-all'](!props.everyItem)"
153+
/>
154+
</th>
155+
<th
156+
class="pl-3"
157+
colspan="10"
158+
width="99%"
159+
>
160+
<v-row class="ma-1">
161+
<data-shared-bread-crumb
162+
:path="path"
163+
:location="notSharedLocation"
164+
@folder-click="setLocation"
165+
@shared-click="resetLocation" />
166+
<v-spacer />
167+
</v-row>
168+
</th>
169+
</tr>
170+
</thead>
171+
</template>
172+
<template #row="{ item }">
173+
<span class="row-content">
174+
<span>{{ item.name }}</span>
175+
<v-icon
176+
v-if="getters['Jobs/datasetRunningState'](item._id)"
177+
color="warning"
178+
class="rotate"
179+
>
180+
mdi-autorenew
181+
</v-icon>
182+
<v-btn
183+
v-if="isAnnotationFolder(item)"
184+
class="ml-2"
185+
x-small
186+
color="primary"
187+
depressed
188+
:to="{ name: 'viewer', params: { id: item._id } }"
189+
>
190+
Launch Annotator
191+
</v-btn>
192+
<span class="owner-text">Shared by {{ item.ownerLogin }}</span>
193+
</span>
194+
</template>
195+
</girder-data-table>
196+
<girder-data-browser
197+
v-else
198+
v-model="locationStore.selected"
199+
:selectable="!getters['Location/locationIsViameFolder']"
200+
:location="notSharedLocation"
201+
:items-per-page-options="itemsPerPageOptions"
202+
:options="options"
203+
@update:location="setLocation($event)"
103204
>
104-
<!-- eslint-disable-next-line -->
105-
<template v-slot:item.annotator="{item}">
205+
<template #breadcrumb>
206+
<data-shared-bread-crumb
207+
:path="path"
208+
:location="notSharedLocation"
209+
@folder-click="setLocation"
210+
@shared-click="resetLocation" />
211+
</template>
212+
<template #row="{ item }">
213+
<span>{{ item.name }}</span>
214+
<v-icon
215+
v-if="getters['Jobs/datasetRunningState'](item._id)"
216+
color="warning"
217+
class="rotate"
218+
>
219+
mdi-autorenew
220+
</v-icon>
106221
<v-btn
107222
v-if="isAnnotationFolder(item)"
108223
class="ml-2"
@@ -114,9 +229,20 @@ export default defineComponent({
114229
Launch Annotator
115230
</v-btn>
116231
</template>
117-
<template #no-data>
118-
<span class="pr-4">No datasets have been shared with you yet.</span>
119-
<a href="https://kitware.github.io/dive/Web-Version/#sharing-data-with-teams">Learn more about sharing</a>
120-
</template>
121-
</v-data-table>
232+
</girder-data-browser>
122233
</template>
234+
235+
<style lang="scss" scoped>
236+
.row-content {
237+
display: inline-block;
238+
width: 81%;
239+
240+
.owner-text {
241+
float: right;
242+
}
243+
}
244+
245+
.no-shared {
246+
padding: 20px;
247+
}
248+
</style>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<script lang="ts">
2+
import {
3+
defineComponent,
4+
PropType,
5+
} from 'vue';
6+
import { RootlessLocationType } from '../store/types';
7+
8+
export default defineComponent({
9+
name: 'DataSharedBreadCrumb',
10+
props: {
11+
path: {
12+
type: Array as PropType<RootlessLocationType[]>,
13+
default: [],
14+
},
15+
},
16+
});
17+
</script>
18+
19+
<template>
20+
<div class="bread-crumb-wrapper secondary darken-2">
21+
<v-icon
22+
small
23+
:color="path.length > 0 ? 'accent' : 'default'"
24+
@click="$emit('shared-click')">
25+
mdi-share-variant
26+
</v-icon>
27+
<div v-for="(pathPart, index) in path" :key="index">
28+
<span class="divider">/</span>
29+
<span v-if="index < path.length - 1" @click="$emit('folder-click', pathPart)" class="accent--text">{{ pathPart.name }}</span>
30+
<span v-else>{{ pathPart.name }}</span>
31+
</div>
32+
</div>
33+
</template>
34+
35+
<style lang="scss" scoped>
36+
.bread-crumb-wrapper {
37+
display: flex;
38+
flex-direction: row;
39+
font-size: 14px;
40+
font-weight: 700;
41+
cursor: pointer;
42+
43+
.divider {
44+
padding: 0 7px;
45+
}
46+
47+
a {
48+
color: rgba(255, 255, 255, 0.5);
49+
}
50+
}
51+
</style>

client/platform/web-girder/views/Home.vue

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import Upload from './Upload.vue';
1818
import DataDetails from './DataDetails.vue';
1919
import Clone from './Clone.vue';
2020
import ShareTab from './ShareTab.vue';
21-
import DataShared from './DataShared.vue';
2221
import { useStore } from '../store/types';
2322
import eventBus from '../eventBus';
2423
@@ -47,7 +46,6 @@ export default defineComponent({
4746
Upload,
4847
RunPipelineMenu,
4948
RunTrainingMenu,
50-
DataShared,
5149
ShareTab,
5250
},
5351
// everything below needs to be refactored to composition-api

server/dive_server/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from .views_annotation import AnnotationResource
1919
from .views_configuration import ConfigurationResource
2020
from .views_dataset import DatasetResource
21-
from .views_override import countJobs, use_private_queue
21+
from .views_override import countJobs, use_private_queue, list_shared_folders
2222
from .views_rpc import RpcResource
2323

2424

@@ -37,6 +37,7 @@ def load(self, info):
3737
# Setup route additions for exsting resources
3838
info['apiRoot'].job.route("GET", ("queued",), countJobs)
3939
info["apiRoot"].user.route("PUT", (":id", "use_private_queue"), use_private_queue)
40+
info["apiRoot"].folder.route("GET", ("shared-folders",), list_shared_folders)
4041
User().exposeFields(AccessType.READ, constants.UserPrivateQueueEnabledMarker)
4142

4243
# Expose Job dataset assocation

0 commit comments

Comments
 (0)