Skip to content

Commit

Permalink
Merge pull request #434 from dfpc-coe/mission-tree
Browse files Browse the repository at this point in the history
Mission Layer UI Updates
  • Loading branch information
ingalls authored Dec 2, 2024
2 parents 64f6abc + 370c89b commit 44e8376
Show file tree
Hide file tree
Showing 14 changed files with 620 additions and 686 deletions.
1 change: 1 addition & 0 deletions api/lib/api/mission-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export default class {
opts?: Static<typeof MissionOptions>
): Promise<TAKList<Static<typeof MissionLayer>>> {
let res;

if (this.#isGUID(name)) {
const url = new URL(`/Marti/api/missions/guid/${this.#encodeName(name)}/layers`, this.api.url);

Expand Down
270 changes: 146 additions & 124 deletions api/package-lock.json

Large diffs are not rendered by default.

320 changes: 165 additions & 155 deletions api/web/package-lock.json

Large diffs are not rendered by default.

35 changes: 1 addition & 34 deletions api/web/src/components/CloudTAK/CoTView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@

<TablerIconButton
title='Zoom To'
@click='zoomTo'
@click='cot.flyTo()'
>
<IconZoomPan
:size='32'
Expand Down Expand Up @@ -607,10 +607,8 @@
<script setup lang='ts'>
import { ref, computed, watch, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router'
import type { LngLatBoundsLike, FlyToOptions, LngLatLike } from 'maplibre-gl'
import type COT from '../../../src/stores/base/cot.ts';
import type { COTType } from '../../../src/types.ts';
import { useMapStore } from '../../../src/stores/map.ts';
import { OriginMode } from '../../../src/stores/base/cot.ts'
import Mission from '../../../src/stores/base/mission.ts'
import {
Expand Down Expand Up @@ -658,7 +656,6 @@ const cotStore = useCOTStore();
import { useProfileStore } from '../../../src/stores/profile.ts';
import { useVideoStore } from '../../../src/stores/videos.ts';
const mapStore = useMapStore();
const profileStore = useProfileStore();
const videoStore = useVideoStore();
const route = useRoute();
Expand Down Expand Up @@ -766,34 +763,4 @@ async function deleteCOT() {
await cotStore.delete(cot.value.id);
router.push('/');
}
function zoomTo() {
if (!cot.value) return;
if (!mapStore.map) throw new Error('Map not initialized');
if (cot.value.geometry.type === "Point") {
const flyTo: FlyToOptions = {
speed: Infinity,
center: cot.value.properties.center as LngLatLike,
zoom: 14
};
if (mapStore.map.getZoom() < 3) {
flyTo.zoom = 4;
}
mapStore.map.flyTo(flyTo)
} else {
mapStore.map.fitBounds(cot.value.bounds() as LngLatBoundsLike, {
maxZoom: 14,
padding: {
top: 20,
bottom: 20,
left: 20,
right: 20
},
speed: Infinity,
})
}
}
</script>
1 change: 1 addition & 0 deletions api/web/src/components/CloudTAK/Menu/Mission.vue
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
</div>

<router-view
:menu='true'
:mission='mission'
:token='token'
:role='role'
Expand Down
232 changes: 125 additions & 107 deletions api/web/src/components/CloudTAK/Menu/Mission/MissionLayerTree.vue
Original file line number Diff line number Diff line change
@@ -1,126 +1,134 @@
<template>
<TablerLoading v-if='loading' />
<div
v-for='layer in layers'
v-else
:key='layer.uid'
>
<div class='col-12 hover-dark d-flex align-items-center px-2 py-1'>
<IconChevronRight
v-if='layer.type === "UID" && !opened.has(layer.uid)'
:size='32'
stroke='1'
class='cursor-pointer'
@click='opened.add(layer.uid)'
/>
<IconChevronDown
v-else-if='layer.type === "UID" && opened.has(layer.uid)'
:size='32'
stroke='1'
class='cursor-pointer'
@click='opened.delete(layer.uid)'
/>

<IconFiles
v-if='layer.type === "CONTENTS"'
:size='32'
stroke='1'
/>
<IconMapPins
v-else-if='layer.type === "UID"'
:size='32'
stroke='1'
/>
<IconFolder
v-else-if='layer.type === "GROUP"'
:size='32'
stroke='1'
/>
<IconMap
v-else-if='layer.type === "MAPLAYER"'
:size='32'
stroke='1'
/>
<IconPin
v-else-if='layer.type === "ITEM"'
:size='32'
stroke='1'
/>
<template v-else>
<SingleFeature
v-for='feat of orphanedFeats'
:key='feat.id'
:delete-button='false'
:feature='orphanedFeats'
:mission='mission'
/>
<div
v-for='layer in layers'
:key='layer.uid'
>
<div class='col-12 hover-dark d-flex align-items-center px-3 py-2'>
<IconChevronRight
v-if='layer.type === "UID" && !opened.has(layer.uid)'
:size='20'
stroke='1'
class='cursor-pointer'
@click='opened.add(layer.uid)'
/>
<IconChevronDown
v-else-if='layer.type === "UID" && opened.has(layer.uid)'
:size='20'
stroke='1'
class='cursor-pointer'
@click='opened.delete(layer.uid)'
/>

<span
class='mx-2'
v-text='layer.name'
/>
<IconFiles
v-if='layer.type === "CONTENTS"'
:size='20'
stroke='1'
/>
<IconMapPins
v-else-if='layer.type === "UID"'
:size='20'
stroke='1'
/>
<IconFolder
v-else-if='layer.type === "GROUP"'
:size='20'
stroke='1'
/>
<IconMap
v-else-if='layer.type === "MAPLAYER"'
:size='20'
stroke='1'
/>
<IconPin
v-else-if='layer.type === "ITEM"'
:size='20'
stroke='1'
/>

<div class='ms-auto btn-list d-flex align-items-center'>
<span
v-if='layer.type === "UID"'
class='mx-3 ms-auto badge border text-white'
:class='{
"bg-blue": layer.uids && layer.uids.length > 0,
"bg-gray": !layer.uids || layer.uids.length === 0
}'
v-text='`${layer.uids ? layer.uids.length : 0} Features`'
class='mx-2'
v-text='layer.name'
/>

<TablerIconButton
v-if='role && role.permissions.includes("MISSION_WRITE")'
title='Edit Name'
:size='24'
@click='edit.add(layer.uid)'
>
<IconPencil stroke='1' />
</TablerIconButton>

<TablerDelete
v-if='role && role.permissions.includes("MISSION_WRITE")'
displaytype='icon'
:size='24'
@delete='deleteLayer(layer)'
/>
<div class='ms-auto btn-list d-flex align-items-center'>
<span
v-if='layer.type === "UID"'
class='mx-3 ms-auto badge border text-white'
:class='{
"bg-blue": layer.uids && layer.uids.length > 0,
"bg-gray": !layer.uids || layer.uids.length === 0
}'
v-text='`${layer.uids ? layer.uids.length : 0} Features`'
/>

<TablerIconButton
v-if='role && role.permissions.includes("MISSION_WRITE")'
title='Edit Name'
:size='24'
@click='edit.add(layer.uid)'
>
<IconPencil stroke='1' />
</TablerIconButton>

<TablerDelete
v-if='role && role.permissions.includes("MISSION_WRITE")'
displaytype='icon'
:size='24'
@delete='deleteLayer(layer)'
/>
</div>
</div>
</div>

<MissionLayerEdit
v-if='edit.has(layer.uid)'
:mission='mission'
:layer='layer'
:role='role'
@cancel='edit.delete(layer.uid)'
@layer='emit("refresh")'
/>
<div
v-else-if='opened.has(layer.uid) && layer.type === "UID"'
class='mx-2'
>
<SingleFeature
v-for='cot of cots(layer)'
:key='cot.id'
:delete-button='false'
:feature='cot'
<MissionLayerEdit
v-if='edit.has(layer.uid)'
:mission='mission'
/>
<MissionLayerTree
v-if='layer.mission_layers && layer.mission_layers.length'
:layers='layer.mission_layers as Array<MissionLayer>'
:feats='feats'
:mission='mission'
:token='token'
:layer='layer'
:role='role'
@refresh='emit("refresh")'
/>
<TablerNone
v-if='!layer.uids || !layer.uids.length'
:create='false'
:compact='true'
class='py-2'
@cancel='edit.delete(layer.uid)'
@layer='emit("refresh")'
/>
<div
v-else-if='opened.has(layer.uid) && layer.type === "UID"'
class='mx-2'
>
<SingleFeature
v-for='cot of cots(layer)'
:key='cot.id'
:delete-button='false'
:feature='cot'
:mission='mission'
/>
<MissionLayerTree
v-if='layer.mission_layers && layer.mission_layers.length'
:layers='layer.mission_layers as Array<MissionLayer>'
:feats='feats'
:mission='mission'
:token='token'
:role='role'
@refresh='emit("refresh")'
/>
<TablerNone
v-if='!layer.uids || !layer.uids.length'
:create='false'
:compact='true'
class='py-2'
/>
</div>
</div>
</div>
</template>
</template>

<script setup lang='ts'>
import { ref } from 'vue';
import { ref, computed } from 'vue';
import type { Mission, MissionLayer, MissionRole, Feature } from '../../../../../src/types.ts';
import Subscription from '../../../../../src/stores/base/mission.ts';
import MissionLayerTree from './MissionLayerTree.vue';
Expand Down Expand Up @@ -151,6 +159,7 @@ const props = defineProps<{
layers: Array<MissionLayer>,
feats: Map<string, Feature>,
mission: Mission,
orphaned?: Set<string>,
token?: string,
role?: MissionRole
}>();
Expand All @@ -159,6 +168,15 @@ const opened = ref<Set<string>>(new Set());
const edit = ref<Set<string>>(new Set());
const loading = ref(false);
const orphanedFeats = computed<Feature[]>(() => {
const feats = [];
for (const uid of props.orphaned || []) {
const feat = props.feats.get(uid);
if (feat) feats.push(feat);
}
return feats;
})
function cots(layer: MissionLayer): Array<Feature> {
const cots = [];
for (const cot of (layer.uids || [])) {
Expand Down
Loading

0 comments on commit 44e8376

Please sign in to comment.