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
7 changes: 7 additions & 0 deletions client/dive-common/components/EditorMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ export default Vue.extend({
icon: 'mdi-format-text',
click: () => this.toggleVisible('text'),
},
{
id: 'tooltip',
type: 'tooltip',
active: this.isVisible('tooltip'),
icon: 'mdi-tooltip-text-outline',
click: () => this.toggleVisible('tooltip'),
},

];
},
Expand Down
33 changes: 30 additions & 3 deletions client/src/components/LayerManager.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script lang="ts">
import { defineComponent, watch, PropType } from '@vue/composition-api';
import {
defineComponent, watch, PropType, Ref, ref,
} from '@vue/composition-api';

import { TrackWithContext } from '../use/useTrackFilters';
import { injectMediaController } from './annotators/useMediaController';
Expand All @@ -10,11 +12,13 @@ import LineLayer from '../layers/AnnotationLayers/LineLayer';

import EditAnnotationLayer, { EditAnnotationTypes } from '../layers/EditAnnotationLayer';
import { FrameDataTrack } from '../layers/LayerTypes';
import TextLayer, { FormatTextRow } from '../layers/TextLayer';
import TextLayer, { FormatTextRow } from '../layers/AnnotationLayers/TextLayer';
import { TrackId } from '../track';
import { geojsonToBound } from '../utils';
import { VisibleAnnotationTypes } from '../layers';

import UILayer from '../layers/UILayers/UILayer';
import ToolTipWidget from '../layers/UILayers/ToolTipWidget.vue';
import { ToolTipWidgetData } from '../layers/UILayers/UILayerTypes';
import {
useEnabledTracks,
useHandler,
Expand Down Expand Up @@ -95,6 +99,16 @@ export default defineComponent({
type: 'rectangle',
});

const uiLayer = new UILayer(annotator);
const hoverOvered: Ref<ToolTipWidgetData[]> = ref([]);
const toolTipWidgetProps = {
color: typeStylingRef.value.color,
dataList: hoverOvered,
selected: selectedTrackIdRef,
stateStyling,
};
const toolTipWidget = uiLayer.addDOMWidget('customToolTip', ToolTipWidget, toolTipWidgetProps);

function updateLayers(
frame: number,
editingTrack: false | EditAnnotationTypes,
Expand All @@ -108,6 +122,8 @@ export default defineComponent({
.search([frame, frame])
.map((str: string) => parseInt(str, 10));

rectAnnotationLayer.setHoverAnnotations(visibleModes.includes('tooltip'));
polyAnnotationLayer.setHoverAnnotations(visibleModes.includes('tooltip'));
const frameData = [] as FrameDataTrack[];
const editingTracks = [] as FrameDataTrack[];
currentFrameIds.forEach(
Expand Down Expand Up @@ -300,6 +316,17 @@ export default defineComponent({
});
editAnnotationLayer.bus.$on('update:selectedIndex',
(index: number, _type: EditAnnotationTypes, key = '') => handler.selectFeatureHandle(index, key));
const annotationHoverTooltip = (found: { trackType: [string, number]; trackId: number}[],
pos: {x: number; y: number}) => {
hoverOvered.value = found.map((item) => ({
type: item.trackType[0],
confidence: item.trackType[1],
trackId: item.trackId,
}));
toolTipWidget.position(pos);
};
rectAnnotationLayer.bus.$on('annotation-hover', annotationHoverTooltip);
polyAnnotationLayer.bus.$on('annotation-hover', annotationHoverTooltip);
},
});
</script>
Expand Down
29 changes: 29 additions & 0 deletions client/src/layers/AnnotationLayers/PolygonLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ interface PolyGeoJSData{
export default class PolygonLayer extends BaseLayer<PolyGeoJSData> {
drawingOther: boolean; //drawing another type of annotation at the same time?

hoverOn: boolean;

constructor(params: BaseLayerParams) {
super(params);
this.drawingOther = true; // Initialized to true in case polygons aren't supported
//Only initialize once, prevents recreating Layer each edit
this.hoverOn = false;
this.initialize();
}

Expand Down Expand Up @@ -57,6 +60,32 @@ export default class PolygonLayer extends BaseLayer<PolyGeoJSData> {
super.initialize();
}


hoverAnnotations(e: GeoEvent) {
if (!this.drawingOther) {
const { found } = this.featureLayer.pointSearch(e.mouse.geo);
this.bus.$emit('annotation-hover', found, e.mouse.geo);
}
}

setHoverAnnotations(val: boolean) {
if (!this.hoverOn && val) {
this.featureLayer.geoOn(
geo.event.feature.mouseover,
(e: GeoEvent) => this.hoverAnnotations(e),
);
this.featureLayer.geoOn(
geo.event.feature.mouseoff,
(e: GeoEvent) => this.hoverAnnotations(e),
);
this.hoverOn = true;
} else if (this.hoverOn && !val) {
this.featureLayer.geoOff(geo.event.feature.mouseover);
this.featureLayer.geoOff(geo.event.feature.mouseoff);
this.hoverOn = false;
}
}
Comment thread
subdavis marked this conversation as resolved.

/**
* Used to set the drawingOther parameter used to change styling if other types are drawn
* and also handle selection clicking between different types
Expand Down
28 changes: 27 additions & 1 deletion client/src/layers/AnnotationLayers/RectangleLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ interface RectGeoJSData{
export default class RectangleLayer extends BaseLayer<RectGeoJSData> {
drawingOther: boolean; //drawing another type of annotation at the same time?

hoverOn: boolean; //to turn over annnotations on

constructor(params: BaseLayerParams) {
super(params);
this.drawingOther = false;
this.hoverOn = false;
//Only initialize once, prevents recreating Layer each edit
this.initialize();
}
Expand Down Expand Up @@ -51,14 +54,37 @@ export default class RectangleLayer extends BaseLayer<RectGeoJSData> {
this.featureLayer.mouseOverOrderClosestBorder,
);
this.featureLayer.geoOn(geo.event.mouseclick, (e: GeoEvent) => {
// If we aren't clicking on an annotation we can deselect the current track
// If we aren't clicking on an annotation we can deselect the current track
if (this.featureLayer.pointSearch(e.geo).found.length === 0) {
this.bus.$emit('annotation-clicked', null, false);
}
});
super.initialize();
}

hoverAnnotations(e: GeoEvent) {
const { found } = this.featureLayer.pointSearch(e.mouse.geo);
this.bus.$emit('annotation-hover', found, e.mouse.geo);
}

setHoverAnnotations(val: boolean) {
if (!this.hoverOn && val) {
this.featureLayer.geoOn(
geo.event.feature.mouseover,
(e: GeoEvent) => this.hoverAnnotations(e),
);
this.featureLayer.geoOn(
geo.event.feature.mouseoff,
(e: GeoEvent) => this.hoverAnnotations(e),
);
this.hoverOn = true;
} else if (this.hoverOn && !val) {
this.featureLayer.geoOff(geo.event.feature.mouseover);
this.featureLayer.geoOff(geo.event.feature.mouseoff);
this.hoverOn = false;
}
}

/**
* Used to set the drawingOther parameter used to change styling if other types are drawn
* and also handle selection clicking between different types
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TypeStyling } from '../use/useStyling';
import BaseLayer, { BaseLayerParams, LayerStyle } from './BaseLayer';
import { FrameDataTrack } from './LayerTypes';
import { TypeStyling } from '../../use/useStyling';
import BaseLayer, { BaseLayerParams, LayerStyle } from '../BaseLayer';
import { FrameDataTrack } from '../LayerTypes';

export interface TextData {
selected: boolean;
Expand Down
74 changes: 74 additions & 0 deletions client/src/layers/UILayers/ToolTipWidget.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script lang="ts">
import {
defineComponent, PropType, Ref,
} from '@vue/composition-api';
import { StateStyles } from '../../use/useStyling';
import { ToolTipWidgetData } from './UILayerTypes';
/*
This Component will be mounted indepedently of the main Vue App
on a GeoJS canvas element. To ensure reactivity between the main Vue App
and this element the props are passed in the initalization function instead of on a template.
This is why reactivate data in this component is utilizing PropType<Ref<data>>.
All references to reactive PropType<Ref<data>> need to be derefernced in the template as well.
*/
export default defineComponent({
name: 'ToolTipWidget',
props: {
color: {
type: Function as PropType<(name: string) => string>,
required: true,
},
stateStyling: {
type: Object as PropType<StateStyles>,
required: true,
},
textSettings: {
type: Function as PropType<(name: string) => { showLabel: boolean; showConfidence: boolean }>,
default: () => ({ showLabel: true, showConfidence: true }),
},
dataList: {
type: Object as PropType<Ref<ToolTipWidgetData[]>>,
default: () => [],
},
selected: {
type: Object as PropType<Ref<number|null>>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PropType<Ref<...>> is a bit of a hack. Props shouldn't be refs, they should be normal values.

I understand that because this component is being mounted as the root of the application, there's no parent to drive changes in its props, but it took me a little bit of code reading to figure that out.

Could the UILayer class have a description comment block to describe this design choice, how it works, and what the special requirements of its widget components are?

required: true,
},
},
setup(props) {
const coloring = (data: ToolTipWidgetData) => {
if (data.trackId === props.selected.value) {
return props.stateStyling.selected.color;
}
return props.color(data.type);
};
return {
coloring,
};
},
});
</script>

<template>
<v-card
v-if="dataList.value.length"
dark
max-width="200px"
class="d-inline-flex pa-2"
>
<div>
<div
v-for="(item, index) in dataList.value"
:key="index"
>
<span
:style="{ color: coloring(item) }"
class="pr-1 pb-1"
>
</span>
<span>{{ `${item.type}:${item.confidence.toFixed(2)}` }}</span>
</div>
</div>
</v-card>
</template>
48 changes: 48 additions & 0 deletions client/src/layers/UILayers/UILayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { createApp } from '@vue/composition-api';
import { MediaController } from '../../components/annotators/mediaControllerType';


interface WidgetPosition {
x: string | number;
y: string | number;
}
export interface DOMWidget {
canvas: () => HTMLElement;
isInViewport: () => boolean;
position: (pos: WidgetPosition) => WidgetPosition;
}
/**
* UILayer provides a way to add Reactive VUE DOM Widgets to GeoJS
* These widgets are created under their own Vue App and can't rely on parent elements
* for reactivity.
* Reactive properties for these components will need to be passed in as Refs and
* dereferenced inside of the Vue component to properly update.
* This will probably change once Vue 3 is adopted and <teleport> can be used
*/
export default class UILayer {
annotator: MediaController;

widgets: Record<string, DOMWidget>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
uiLayer: any;

constructor(annotator: MediaController) {
this.annotator = annotator;
this.widgets = {};
this.uiLayer = this.annotator.geoViewerRef.value.createLayer('ui');
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
addDOMWidget(name: string, component: any, props: any, position = { x: 0, y: 0 }) {
const widget: DOMWidget = this.uiLayer.createWidget('dom', { position });
widget.canvas().setAttribute('id', name);
const parent = widget.canvas();
const div = document.createElement('div');
const element = parent.appendChild(div);
createApp(component, props).mount(element);
this.widgets[name] = widget;

return widget;
}
}
5 changes: 5 additions & 0 deletions client/src/layers/UILayers/UILayerTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ToolTipWidgetData {
type: string;
confidence: number;
trackId: number;
}
4 changes: 2 additions & 2 deletions client/src/layers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import LineLayer from './AnnotationLayers/LineLayer';
import PointLayer from './AnnotationLayers/PointLayer';
import PolygonLayer from './AnnotationLayers/PolygonLayer';
import RectangleLayer from './AnnotationLayers/RectangleLayer';
import TextLayer from './TextLayer';
import TextLayer from './AnnotationLayers/TextLayer';

type VisibleAnnotationTypes = EditAnnotationTypes | 'text';
type VisibleAnnotationTypes = EditAnnotationTypes | 'text' | 'tooltip';
export {
EditAnnotationLayer,
EditAnnotationTypes,
Expand Down