Skip to content
Open
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
17 changes: 16 additions & 1 deletion client/src/components/ColorSchemeDialog.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, watch } from 'vue';
import { onMounted, watch, PropType } from 'vue';
import useState from '@use/useState';
import ColorSchemeSelect from './ColorSchemeSelect.vue';
import ColorPickerMenu from './ColorPickerMenu.vue';
Expand All @@ -11,6 +11,13 @@ const {
backgroundColor,
} = useState();

defineProps({
displayMode: {
type: String as PropType<'dialog' | 'menu'>,
default: 'dialog',
},
});

onMounted(() => {
const localBackgroundColor = localStorage.getItem('spectrogramBackgroundColor');
if (localBackgroundColor) {
Expand Down Expand Up @@ -40,11 +47,19 @@ watch(colorScheme, () => {
<v-tooltip>
<template #activator="{ props: tooltipProps }">
<v-icon
v-if="displayMode === 'dialog'"
v-bind="{ ...modalProps, ...tooltipProps }"
size="30"
>
mdi-palette
</v-icon>
<span
v-else
v-bind="{ ...modalProps, ...tooltipProps }"
>
<v-icon size="30">mdi-palette</v-icon>
<span>Change Color Scheme</span>
</span>
</template>
View spectrogram color options
</v-tooltip>
Expand Down
7 changes: 6 additions & 1 deletion client/src/components/RecordingInfoDialog.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { defineComponent, onMounted, ref, Ref, watch } from 'vue';
import { defineComponent, onMounted, ref, Ref, watch, PropType } from 'vue';
import { getRecording, Recording } from '../api/api';
import useState from '@use/useState';
import RecordingInfoDisplay from './RecordingInfoDisplay.vue';
Expand All @@ -14,6 +14,10 @@ export default defineComponent({
type: String,
required: true,
},
displayMode: {
type: String as PropType<'both' | 'metadata' | 'map'>,
default: 'both',
}
},
emits: ['close'],
setup(props) {
Expand All @@ -39,6 +43,7 @@ export default defineComponent({
v-if="recordingInfo"
:recording-info="recordingInfo"
:minimal-metadata="configuration.mark_annotations_completed_enabled"
:display-mode="displayMode"
@close="$emit('close')"
/>
</template>
65 changes: 44 additions & 21 deletions client/src/components/RecordingInfoDisplay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,29 @@ export default defineComponent({
minimalMetadata: {
type: Boolean,
default: false,
},
displayMode: {
type: String as PropType<'both' | 'metadata' | 'map'>,
default: 'both',
}
},
emits: ['close'],
setup(props) {
const location = computed(() => {
if (!props.minimalMetadata && props.recordingInfo.recording_location) {
if (props.recordingInfo.recording_location) {
return {
x: props.recordingInfo.recording_location.coordinates[0],
y:props.recordingInfo.recording_location.coordinates[1]
};
}
return undefined;
});
const showMetadata = computed(() => props.displayMode === 'both' || props.displayMode === 'metadata');
const showMap = computed(() => props.displayMode === 'both' || props.displayMode === 'map');
return {
location,
showMetadata,
showMap,
};
},
});
Expand All @@ -46,37 +54,52 @@ export default defineComponent({
{{ recordingInfo.name }}
</v-card-title>
<v-card-text>
<v-row>
<div><b>Filename:</b><span>{{ recordingInfo.audio_file }}</span></div>
</v-row>
<v-row>
<div><b>Owner:</b><span>{{ recordingInfo.owner_username }}</span></div>
</v-row>
<v-row>
<div><b>Time:</b><span>{{ recordingInfo.recorded_date }}</span> <span> {{ recordingInfo.recorded_time }}</span></div>
</v-row>
<v-row v-if="!minimalMetadata">
<div><b>Equipment:</b><span>{{ recordingInfo.equipment || 'None' }}</span></div>
</v-row>
<v-row v-if="!minimalMetadata">
<div><b>Comments:</b><span>{{ recordingInfo.comments || 'None' }}</span></div>
</v-row>
<v-row>
<div v-if="showMetadata">
<v-row>
<div><b>Filename:</b><span>{{ recordingInfo.audio_file }}</span></div>
</v-row>
<v-row>
<div><b>Owner:</b><span>{{ recordingInfo.owner_username }}</span></div>
</v-row>
<v-row>
<div><b>Time:</b><span>{{ recordingInfo.recorded_date }}</span> <span> {{ recordingInfo.recorded_time }}</span></div>
</v-row>
<v-row v-if="!minimalMetadata">
<div><b>Equipment:</b><span>{{ recordingInfo.equipment || 'None' }}</span></div>
</v-row>
<v-row v-if="!minimalMetadata">
<div><b>Comments:</b><span>{{ recordingInfo.comments || 'None' }}</span></div>
</v-row>
</div>
<v-row
v-if="recordingInfo.grts_cell_id && showMetadata && displayMode === 'both' || displayMode === 'metadata'"
class="mt-2"
>
<div><b>GRTS CellId:</b><span>{{ recordingInfo.grts_cell_id }}</span></div>
</v-row>
<v-row v-if="recordingInfo.recording_location">
<v-spacer />
<v-row
v-if="recordingInfo.recording_location && showMap"
class="justify-center"
>
<map-location
:editor="false"
:size="{width: 400, height: 400}"
:location="location"
:grts-cell-id="recordingInfo.grts_cell_id || undefined"
/>
<v-spacer />
</v-row>
<v-row
v-if="recordingInfo.grts_cell_id && !showMetadata && showMap"
class="justify-center mt-4"
>
<div class="text-center">
<b>GRTS CellId:</b>
<span>{{ recordingInfo.grts_cell_id }}</span>
</div>
</v-row>

<div
v-if="recordingInfo.site_name"
v-if="recordingInfo.site_name && showMetadata"
class="mt-5"
>
<v-row><h3>Guano Metadata</h3></v-row>
Expand Down
142 changes: 3 additions & 139 deletions client/src/components/SpectrogramImageContentMenu.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { ref } from 'vue';
import useState from '@use/useState';

defineProps<{
Expand All @@ -8,68 +8,14 @@ defineProps<{
}>();

const {
contoursEnabled,
imageOpacity,
contourOpacity,
contoursLoading,
viewMaskOverlay,
maskOverlayOpacity,
setContoursEnabled,
} = useState();

const hover = ref(false);

// When unchecked via checkbox we save the value to restore on check.
// When opacity is slid to 0 we clear these so that checking restores to 1.0.
const lastImageOpacity = ref<number | undefined>(undefined);
const lastContourOpacity = ref<number | undefined>(undefined);
const uncheckingImage = ref(false);
const uncheckingContour = ref(false);

watch(imageOpacity, (newVal) => {
if (newVal === 0 && !uncheckingImage.value) {
lastImageOpacity.value = undefined;
}
});
watch(contourOpacity, (newVal) => {
if (newVal === 0 && !uncheckingContour.value) {
lastContourOpacity.value = undefined;
}
});

function onImageVisibilityChange(visible: boolean | null) {
if (visible) {
imageOpacity.value = lastImageOpacity.value ?? 1.0;
} else {
lastImageOpacity.value = imageOpacity.value;
imageOpacity.value = 0;
uncheckingImage.value = false;
}
}

function onContourVisibilityChange(visible: boolean | null) {
if (visible) {
contourOpacity.value = lastContourOpacity.value ?? 1.0;
} else {
lastContourOpacity.value = contourOpacity.value;
contourOpacity.value = 0;
uncheckingContour.value = true;
}
}

// Mask and contours are mutually exclusive
function onMaskOverlayChange(checked: boolean) {
viewMaskOverlay.value = checked;
if (checked) {
setContoursEnabled(false);
}
}

function onContoursEnabledChange(checked: boolean) {
setContoursEnabled(checked);
if (checked) {
viewMaskOverlay.value = false;
}
}
</script>

Expand All @@ -89,34 +35,25 @@ function onContoursEnabledChange(checked: boolean) {
>
<template #activator="{ props }">
<v-icon
v-if="!contoursLoading"
v-bind="props"
size="25"
:color="viewMaskOverlay || contoursEnabled ? 'blue' : ''"
:color="viewMaskOverlay ? 'blue' : ''"
>
mdi-vector-curve
</v-icon>
<v-progress-circular
v-else
indeterminate
size="25"
color="primary"
/>
</template>
<v-card
min-width="300"
class="pa-3"
>
<v-card-title class="text-subtitle-1">
Overlay & Contour Options
Mask Overlay Options
</v-card-title>
<v-card-text>
<!-- Show mask OR contours (mutually exclusive) -->
<template v-if="hasMaskUrls">
<div class="text-caption mb-2 d-flex align-center">
<v-checkbox
:model-value="viewMaskOverlay"
:disabled="contoursEnabled"
hide-details
density="compact"
class="mr-1 mt-0 flex-shrink-0"
Expand All @@ -139,79 +76,6 @@ function onContoursEnabledChange(checked: boolean) {
/>
</template>
</template>

<v-divider
v-if="hasMaskUrls"
class="my-2"
/>

<!-- Show contours (with performance warning icon) -->
<div class="text-caption mb-2 d-flex align-center">
<v-checkbox
:model-value="contoursEnabled"
:disabled="viewMaskOverlay"
hide-details
density="compact"
class="mr-1 mt-0 flex-shrink-0"
label="Show contours"
@update:model-value="onContoursEnabledChange($event === true)"
/>
<v-tooltip location="top">
<template #activator="{ props: tooltipProps }">
<v-icon
v-bind="tooltipProps"
size="small"
color="warning"
class="ml-1"
>
mdi-alert
</v-icon>
</template>
<span>Contours may not be performant on large recordings.</span>
</v-tooltip>
</div>

<!-- Image & contour opacity (only when contours enabled) -->
<template v-if="contoursEnabled">
<div class="text-caption mb-2 d-flex align-center">
<v-checkbox
:model-value="imageOpacity > 0"
hide-details
density="compact"
class="mr-1 mt-0 flex-shrink-0"
@update:model-value="onImageVisibilityChange"
/>
Image opacity {{ (imageOpacity * 100).toFixed(2) }}%
</div>
<v-slider
v-model="imageOpacity"
:min="0"
:max="1"
:step="0.05"
hide-details
density="compact"
class="mb-2"
/>
<div class="text-caption mb-2 d-flex align-center">
<v-checkbox
:model-value="contourOpacity > 0"
hide-details
density="compact"
class="mr-1 mt-0 flex-shrink-0"
@update:model-value="onContourVisibilityChange"
/>
Contour opacity {{ (contourOpacity * 100).toFixed(2) }}%
</div>
<v-slider
v-model="contourOpacity"
:min="0"
:max="1"
:step="0.05"
hide-details
density="compact"
class="mb-3"
/>
</template>
</v-card-text>
</v-card>
</v-menu>
Expand Down
6 changes: 6 additions & 0 deletions client/src/components/geoJS/geoJSUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,19 @@ const useGeoJS = () => {
opacity: number
) => {
if (images.length === 0) return;
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
return;
}
layer.node().css("opacity", String(opacity));
while (features.length > images.length) {
const feature = features.pop();
if (feature) layer.removeFeature(feature);
}
let previousWidth = 0;
const totalBaseWidth = images.reduce((sum, img) => sum + img.naturalWidth, 0);
if (!Number.isFinite(totalBaseWidth) || totalBaseWidth <= 0) {
return;
}
images.forEach((image, index) => {
const scale = width / totalBaseWidth;
const currentWidth = image.width * scale;
Expand Down
3 changes: 2 additions & 1 deletion client/src/use/usePulseMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ function getInitialLabelsMode(): PulseMetadataLabelsMode {

const pulseMetadataList: Ref<PulseMetadata[]> = ref([]);
const pulseMetadataLoading = ref(false);
const viewPulseMetadataLayer = ref(stored.viewPulseMetadataLayer ?? false);
// Default to showing pulse metadata unless user explicitly turned it off.
const viewPulseMetadataLayer = ref(stored.viewPulseMetadataLayer ?? true);

async function loadPulseMetadata(recordingId: number) {
pulseMetadataLoading.value = true;
Expand Down
Loading