Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions client/dive-common/components/SidebarContext.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export default defineComponent({
default: 300,
},
},
components: context.componentMap,
setup() {
return { context };
},
Expand Down Expand Up @@ -40,9 +39,7 @@ export default defineComponent({
</v-btn>
</v-card-title>
<div class="sidebar-content">
<component
:is="context.state.active"
/>
<slot v-bind="{ name: context.state.active }" />
</div>
</v-card>
</div>
Expand Down
1 change: 0 additions & 1 deletion client/dive-common/components/TypeThreshold.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { DefaultConfidence } from 'vue-media-annotator/use/useTrackFilters';

export default defineComponent({
name: 'TypeThreshold',
description: 'Threshold Controls',

components: { ConfidenceFilter },

Expand Down
7 changes: 4 additions & 3 deletions client/dive-common/components/Viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import UserGuideButton from 'dive-common/components/UserGuideButton.vue';
import DeleteControls from 'dive-common/components/DeleteControls.vue';
import ControlsContainer from 'dive-common/components/ControlsContainer.vue';
import Sidebar from 'dive-common/components/Sidebar.vue';
import SidebarContext from 'dive-common/components/SidebarContext.vue';
import { useModeManager, useSave } from 'dive-common/use';
import clientSettingsSetup, { clientSettings } from 'dive-common/store/settings';
import { useApi, FrameImage, DatasetType } from 'dive-common/apispec';
Expand All @@ -48,7 +47,6 @@ export default defineComponent({
ControlsContainer,
DeleteControls,
Sidebar,
SidebarContext,
LayerManager,
VideoAnnotator,
ImageAnnotator,
Expand Down Expand Up @@ -422,6 +420,8 @@ export default defineComponent({
intervalTree,
mergeList,
pendingSaveCount,
progress,
revisionId: toRef(props, 'revision'),
trackMap,
filteredTracks,
typeStyling,
Expand Down Expand Up @@ -555,6 +555,7 @@ export default defineComponent({
{{ item }} {{ item === defaultCamera ? '(Default)': '' }}
</template>
</v-select>
<slot name="extension-right" />
</template>

<slot name="title-right" />
Expand Down Expand Up @@ -666,7 +667,7 @@ export default defineComponent({
</v-progress-circular>
</div>
</v-col>
<SidebarContext />
<slot name="right-sidebar" />
</v-row>
</v-main>
</template>
Expand Down
43 changes: 32 additions & 11 deletions client/dive-common/store/context.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,57 @@
import Install, { reactive } from '@vue/composition-api';
import Vue from 'vue';
import Vue, { VueConstructor } from 'vue';
/* Components */
import TypeThreshold from 'dive-common/components/TypeThreshold.vue';

Vue.use(Install);


const componentMap = {
TypeThreshold,
};

type ContextType = keyof typeof componentMap;

interface ContextState {
active: ContextType | null;
active: string | null;
}

interface ComponentMapItem {
description: string;
component: VueConstructor<Vue>;
}

const state: ContextState = reactive({
active: null,
});

function toggle(active: ContextType | null) {
const componentMap: Record<string, ComponentMapItem> = {
TypeThreshold: {
description: 'Threshold Controls',
component: TypeThreshold,
},
};

function register(item: ComponentMapItem) {
componentMap[item.component.name] = item;
}

function getComponents() {
const components: Record<string, VueConstructor<Vue>> = {};
Object.values(componentMap).forEach((v) => {
components[v.component.name] = v.component;
});
return components;
}

function toggle(active: string | null) {
Comment thread
BryonLewis marked this conversation as resolved.
if (active && state.active === active) {
state.active = null;
} else {
} else if (active === null || active in componentMap) {
state.active = active;
} else {
throw new Error(`${active} is not a valid context component`);
}
window.dispatchEvent(new Event('resize'));
}

export default {
toggle,
register,
getComponents,
componentMap,
state,
};
43 changes: 42 additions & 1 deletion client/dive-common/use/useRequest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { reactive, toRefs } from '@vue/composition-api';
import { reactive, shallowRef, toRefs } from '@vue/composition-api';
import { AxiosResponse } from 'axios';
import { getResponseError } from 'vue-media-annotator/utils';

export default function useRequest() {
Expand Down Expand Up @@ -36,3 +37,43 @@ export default function useRequest() {
reset,
};
}

export function usePaginatedRequest<T>() {
const main = useRequest();
const paginationParams = reactive({
totalCount: 0,
offset: 0,
limit: 20,
});
const allPages = shallowRef([] as T[]);

function reset() {
paginationParams.totalCount = 0;
paginationParams.offset = 0;
paginationParams.limit = 20;
allPages.value = [];
main.reset();
}

async function loadNextPage(
func: (limit: number, offset: number) => Promise<AxiosResponse<T[]>>,
) {
const wrapped = () => main.request(() => func(paginationParams.limit, paginationParams.offset));
const nextOffset = paginationParams.offset + paginationParams.limit;
const maxOffset = (paginationParams.totalCount + paginationParams.limit);
if (nextOffset < maxOffset || main.count.value === 0) {
const resp = await wrapped();
paginationParams.offset = nextOffset;
paginationParams.totalCount = Number.parseInt(resp.headers['girder-total-count'], 10);
allPages.value = allPages.value.concat(resp.data);
}
}

return {
...main,
...toRefs(paginationParams),
allPages,
reset,
loadNextPage,
};
}
12 changes: 11 additions & 1 deletion client/platform/desktop/frontend/components/ViewerLoader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
import Viewer from 'dive-common/components/Viewer.vue';
import RunPipelineMenu from 'dive-common/components/RunPipelineMenu.vue';
import ImportAnnotations from 'dive-common//components/ImportAnnotations.vue';

import SidebarContext from 'dive-common/components/SidebarContext.vue';
import context from 'dive-common/store/context';
import { usePrompt } from 'dive-common/vue-utilities/prompt-service';
import Export from './Export.vue';
import JobTab from './JobTab.vue';
Expand All @@ -33,8 +34,10 @@ export default defineComponent({
Export,
JobTab,
RunPipelineMenu,
SidebarContext,
Viewer,
ImportAnnotations,
...context.getComponents(),
},
props: {
id: { // always the base ID
Expand Down Expand Up @@ -143,5 +146,12 @@ export default defineComponent({
:button-options="buttonOptions"
/>
</template>
<template #right-sidebar>
<SidebarContext>
<template #default="{ name }">
<component :is="name" />
</template>
</SidebarContext>
</template>
</Viewer>
</template>
25 changes: 25 additions & 0 deletions client/platform/web-girder/api/annotation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import { SaveDetectionsArgs } from 'dive-common/apispec';
import { TrackData } from 'vue-media-annotator/track';
import girderRest from 'platform/web-girder/plugins/girder';

export interface Revision {
additions: Readonly<number>;
deletions: Readonly<number>;
author_id: Readonly<string>;
author_name: Readonly<string>;
created: Readonly<string>;
dataset: Readonly<string>;
description: Readonly<string>;
revision: Readonly<number>;
}

function loadDetections(folderId: string, revision?: number) {
const params: Record<string, unknown> = { folderId };
if (revision !== undefined) {
Expand All @@ -10,6 +21,19 @@ function loadDetections(folderId: string, revision?: number) {
return girderRest.get<{ [key: string]: TrackData }>('dive_annotation', { params });
}

function loadRevisions(
folderId: string,
limit?: number,
offset?: number,
sort?: string,
) {
return girderRest.get<Revision[]>('dive_annotation/revision', {
params: {
folderId, sortdir: -1, limit, offset, sort,
},
});
}

function saveDetections(folderId: string, args: SaveDetectionsArgs) {
return girderRest.patch('dive_annotation', {
upsert: args.upsert,
Expand All @@ -21,5 +45,6 @@ function saveDetections(folderId: string, args: SaveDetectionsArgs) {

export {
loadDetections,
loadRevisions,
saveDetections,
};
4 changes: 2 additions & 2 deletions client/platform/web-girder/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ const router = new Router({
beforeEnter,
},
{
path: '/viewer/:id/rev/:revision',
name: 'viewer',
path: '/viewer/:id/revision/:revision',
name: 'revision viewer',
component: ViewerLoader,
props: true,
beforeEnter,
Expand Down
6 changes: 6 additions & 0 deletions client/platform/web-girder/views/Clone.vue
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ export default defineComponent({
Create a new clone
</v-card-title>
<v-card-text>
<v-alert
v-if="revision"
type="info"
>
Revision {{ revision }} selected.
</v-alert>
<v-alert
v-if="cloneError"
type="error"
Expand Down
29 changes: 24 additions & 5 deletions client/platform/web-girder/views/Export.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import {
computed, defineComponent, ref, shallowRef, toRef, watch, PropType, Ref,
} from '@vue/composition-api';
import { usePendingSaveCount, useHandler, useCheckedTypes } from 'vue-media-annotator/provides';
import {
usePendingSaveCount, useHandler, useCheckedTypes, useRevisionId,
} from 'vue-media-annotator/provides';
import AutosavePrompt from 'dive-common/components/AutosavePrompt.vue';
import { useRequest } from 'dive-common/use';
import {
Expand Down Expand Up @@ -43,10 +45,12 @@ export default defineComponent({
let save = () => Promise.resolve();
let pendingSaveCount = ref(0);
let checkedTypes = ref([] as readonly string[]);
let revisionId = ref(null as null | number);
if (props.blockOnUnsaved) {
save = useHandler().save;
pendingSaveCount = usePendingSaveCount();
checkedTypes = useCheckedTypes();
revisionId = useRevisionId();
}

async function doExport({ forceSave = false, url }: { url?: string; forceSave?: boolean }) {
Expand Down Expand Up @@ -103,7 +107,10 @@ export default defineComponent({
return {
exportAllUrl: getUri({
url: 'dive_dataset/export',
params: { ...params, folderIds: JSON.stringify([singleDataSetId.value]) },
params: {
...params,
folderIds: JSON.stringify([singleDataSetId.value]),
},
}),
exportMediaUrl: dataset.value?.type === 'video'
? datasetMedia.value?.video?.url
Expand All @@ -118,7 +125,11 @@ export default defineComponent({
}),
exportDetectionsUrl: getUri({
url: 'dive_annotation/export',
params: { ...params, folderId: singleDataSetId.value },
params: {
...params,
folderId: singleDataSetId.value,
revisionId: revisionId.value,
},
}),
exportConfigurationUrl: getUri({
url: `dive_dataset/${singleDataSetId.value}/configuration`,
Expand Down Expand Up @@ -153,6 +164,7 @@ export default defineComponent({
menuOpen,
exportUrls,
checkedTypes,
revisionId,
savePrompt,
singleDataSetId,
doExport,
Expand Down Expand Up @@ -207,6 +219,13 @@ export default defineComponent({
<v-card-title>
Download options
</v-card-title>
<v-alert
v-if="revisionId"
type="info"
tile
>
Revision {{ revisionId }} selected
</v-alert>
<v-alert
v-if="error"
color="error"
Expand Down Expand Up @@ -236,7 +255,7 @@ export default defineComponent({
</v-card-actions>

<v-card-text class="pb-2">
<div>Get latest detections csv only</div>
<div>Get latest annotation csv only</div>
<template v-if="dataset.confidenceFilters">
<v-checkbox
v-model="excludeBelowThreshold"
Expand Down Expand Up @@ -275,7 +294,7 @@ export default defineComponent({
:disabled="!exportUrls.exportDetectionsUrl"
@click="doExport({ url: exportUrls && exportUrls.exportDetectionsUrl })"
>
<span v-if="exportUrls.exportDetectionsUrl">detections</span>
<span v-if="exportUrls.exportDetectionsUrl">annotations</span>
<span v-else>detections unavailable</span>
</v-btn>
</v-card-actions>
Expand Down
Loading