diff --git a/api/web/src/components/CloudTAK/Menu/Channels.vue b/api/web/src/components/CloudTAK/Menu/Channels.vue index 9bd1cdff4..2aa8593ca 100644 --- a/api/web/src/components/CloudTAK/Menu/Channels.vue +++ b/api/web/src/components/CloudTAK/Menu/Channels.vue @@ -22,7 +22,7 @@ /> - + - + - - + + > @@ -30,7 +24,7 @@ - + - diff --git a/api/web/src/components/CloudTAK/Menu/Packages.vue b/api/web/src/components/CloudTAK/Menu/Packages.vue index 50d72b531..c08b38fb4 100644 --- a/api/web/src/components/CloudTAK/Menu/Packages.vue +++ b/api/web/src/components/CloudTAK/Menu/Packages.vue @@ -48,7 +48,7 @@ - + + + + + + + No Channels are selected + + + No Data Syncs have been subscribed to + + + + + + + diff --git a/api/web/src/components/CloudTAK/util/NoChannelsInfo.vue b/api/web/src/components/CloudTAK/util/NoChannelsInfo.vue deleted file mode 100644 index e7fe08129..000000000 --- a/api/web/src/components/CloudTAK/util/NoChannelsInfo.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - No Channels are selected - - - - - diff --git a/api/web/src/components/CloudTAK/util/SelectFeats.vue b/api/web/src/components/CloudTAK/util/SelectFeats.vue index 466322bea..f26508c9a 100644 --- a/api/web/src/components/CloudTAK/util/SelectFeats.vue +++ b/api/web/src/components/CloudTAK/util/SelectFeats.vue @@ -4,7 +4,7 @@ style='max-height: 400px' > - + Selected Features @@ -47,7 +47,7 @@ - + + + + @@ -124,6 +133,7 @@ import { TablerIconButton } from '@tak-ps/vue-tabler'; import Share from './Share.vue'; +import ShareToMission from './ShareToMission.vue'; const cotStore = useCOTStore(); @@ -134,8 +144,14 @@ const props = defineProps({ } }); +enum ShareType { + NONE = 'none', + MISSION = 'mission', + USERS = 'users' +} + const loading = ref(false); -const share = ref(false); +const share = ref(ShareType.NONE); async function deleteFeatures() { loading.value = true; diff --git a/api/web/src/components/CloudTAK/util/ShareToMission.vue b/api/web/src/components/CloudTAK/util/ShareToMission.vue new file mode 100644 index 000000000..5b52b03eb --- /dev/null +++ b/api/web/src/components/CloudTAK/util/ShareToMission.vue @@ -0,0 +1,109 @@ + + + + Add to Mission + + + + + + + + + + + + + + + + + + + + + Add to Mission + + + + + Cancel + + + + + + + + diff --git a/api/web/src/stores/base/mission.ts b/api/web/src/stores/base/mission.ts index 47aa886a6..98eef50a0 100644 --- a/api/web/src/stores/base/mission.ts +++ b/api/web/src/stores/base/mission.ts @@ -3,6 +3,7 @@ import { std, stdurl } from '../../std.ts'; import type { Mission, MissionLog, + MissionList, MissionLogList, MissionSubscriptions } from '../../types.ts'; @@ -43,6 +44,25 @@ export default class Subscription { return new Subscription(mission, logs); } + static async list(opts: { + passwordProtected?: boolean; + defaultRole?: boolean; + } = {}): Promise { + if (opts.passwordProtected === undefined) opts.passwordProtected = true; + if (opts.defaultRole === undefined) opts.defaultRole = true; + + const url = stdurl('/api/marti/mission'); + url.searchParams.append('passwordProtected', String(opts.passwordProtected)); + url.searchParams.append('defaultRole', String(opts.defaultRole)); + this.list = await std(url); + + const list = await std(url, { + method: 'GET', + }) as MissionList; + + return list; + } + static headers(token?: string): Record { const headers: Record = {}; if (token) headers.MissionAuthorization = token; diff --git a/api/web/src/stores/cots.ts b/api/web/src/stores/cots.ts index 0b947bd9c..118f5c1ab 100644 --- a/api/web/src/stores/cots.ts +++ b/api/web/src/stores/cots.ts @@ -139,7 +139,9 @@ export const useCOTStore = defineStore('cots', { headers: Subscription.headers(token) }) as FeatureCollection; - for (const feat of fc.features) this.add(feat as Feature, guid); + for (const feat of fc.features) { + this.add(feat as Feature, guid); + } return this.collection(sub.cots) }, diff --git a/api/web/src/stores/map.ts b/api/web/src/stores/map.ts index 56338ecb1..c478dd099 100644 --- a/api/web/src/stores/map.ts +++ b/api/web/src/stores/map.ts @@ -371,29 +371,24 @@ export const useMapStore = defineStore('cloudtak', { })); // Data Syncs are specially loaded as they are dynamic - const promises = []; for (const overlay of this.overlays) { - promises.push(async () => { - if (overlay.mode === 'mission' && overlay.mode_id) { - const cotStore = useCOTStore(); - const source = map.getSource(String(overlay.id)); - if (!source) return; - - try { - // @ts-expect-error Source.setData is not defined - source.setData(await cotStore.loadMission(overlay.mode_id, overlay.token)); - } catch (err) { - // TODO: Handle this gracefully - // The Mission Sync is either: - // - Deleted - // - Part of a channel that is no longer active - overlay._error = err instanceof Error ? err : new Error(String(err)); - } + if (overlay.mode === 'mission' && overlay.mode_id) { + const cotStore = useCOTStore(); + const source = map.getSource(String(overlay.id)); + + if (!source) continue; + + try { + await this.updateMissionData(overlay.mode_id); + } catch (err) { + // TODO: Handle this gracefully + // The Mission Sync is either: + // - Deleted + // - Part of a channel that is no longer active + overlay._error = err instanceof Error ? err : new Error(String(err)); } - }); + } } - - await Promise.allSettled(promises); }, /** * Determine if the feature is from the CoT store or a clicked VT feature diff --git a/api/web/src/types.ts b/api/web/src/types.ts index c6cec5597..2e9f6e107 100644 --- a/api/web/src/types.ts +++ b/api/web/src/types.ts @@ -26,6 +26,7 @@ export type User = paths["/user/{:username}"]["get"]["responses"]["200"]["conten export type UserList = paths["/user"]["get"]["responses"]["200"]["content"]["application/json"]; export type Mission = paths["/marti/missions/{:name}"]["get"]["responses"]["200"]["content"]["application/json"]; +export type MissionList = paths["/marti/missions"]["get"]["responses"]["200"]["content"]["application/json"]; export type MissionLog = paths["/marti/missions/{:name}/log/{:logid}"]["patch"]["responses"]["200"]["content"]["application/json"]["data"]; export type MissionLogList = paths["/marti/missions/{:name}/log"]["get"]["responses"]["200"]["content"]["application/json"]; export type MissionSubscriptions = paths["/marti/missions/{:name}/subscriptions/roles"]["get"]["responses"]["200"]["content"]["application/json"]["data"];