update popup, update pages

This commit is contained in:
Will Freeman
2025-06-01 19:04:31 -06:00
parent 82ea7ff20a
commit af91ab3a9c
5 changed files with 175 additions and 76 deletions
+67 -61
View File
@@ -1,56 +1,51 @@
<template>
<v-sheet min-width="240">
<v-list density="compact">
<v-list-item v-if="isFaceRecognition">
<div class="d-flex align-center">
<v-icon start>mdi-face-recognition</v-icon> <b>Face Recognition</b>
</div>
</v-list-item>
<v-sheet width="220">
<!-- TODO: if a field is unknown, prompt user to edit it -->
<v-img cover width="100%" height="120px" src="/alprs/flock-3.jpg" />
<v-list density="compact" class="my-2">
<v-list-item>
<div class="d-flex align-center">
<v-icon start>mdi-cctv</v-icon> <b>License Plate Reader</b>
</div>
</v-list-item>
<v-list-item v-if="isFaceRecognition">
<v-icon start>mdi-adjust</v-icon> <b>Omnidirectional</b>
</v-list-item>
<v-list-item v-else>
<div class="d-flex align-center">
<v-icon start>mdi-compass-outline</v-icon> <b>{{ cardinalDirection }}</b>
</div>
<template v-slot:prepend>
<v-icon icon="mdi-police-badge"></v-icon>
</template>
<v-list-item-subtitle style="font-size: 1em">
Operated by
</v-list-item-subtitle>
<b>
<span style="font-size: 1.25em">
{{ abbreviatedOperator }}
</span>
</b>
<template v-slot:append>
<v-btn icon size="small" variant="text" :href="transparencyLink" target="_blank" color="primary">
<v-icon icon="mdi-open-in-new"></v-icon>
</v-btn>
</template>
</v-list-item>
<v-divider class="my-2" />
<v-list-item>
<div class="d-flex align-center">
<v-icon start>mdi-domain</v-icon> <b>
<span v-if="alpr.tags.manufacturer">
{{ alpr.tags.manufacturer }}
</span>
<span v-else-if="alpr.tags.brand">
{{ alpr.tags.brand }}
</span>
<span v-else>
Unspecified Manufacturer
</span>
</b>
</div>
</v-list-item>
<v-list-item>
<div class="d-flex align-center">
<v-icon start>mdi-account-tie</v-icon>
<b>
<span v-if="alpr.tags.operator">
{{ alpr.tags.operator }}
</span>
<span v-else>
Unspecified Operator
</span>
</b>
</div>
<template v-slot:prepend>
<v-icon icon="mdi-factory"></v-icon>
</template>
<v-list-item-subtitle style="font-size: 1em">
Made by
</v-list-item-subtitle>
<b>
<span style="font-size: 1.25em">
{{ manufacturer }}
</span>
</b>
</v-list-item>
</v-list>
<div class="text-center">
<v-btn target="_blank" size="x-small" :href="osmNodeLink(props.alpr.id)" variant="text" color="grey-darken-1"><v-icon start>mdi-open-in-new</v-icon>View on OSM</v-btn>
<v-btn target="_blank" size="x-small" :href="osmNodeLink(props.alpr.id)" variant="text" color="grey"><v-icon start>mdi-open-in-new</v-icon>View on OSM</v-btn>
</div>
</v-sheet>
</template>
@@ -59,7 +54,7 @@
import { computed } from 'vue';
import type { PropType } from 'vue';
import type { ALPR } from '@/types';
import { VIcon, VList, VSheet, VListItem, VBtn } from 'vuetify/components';
import { VIcon, VList, VSheet, VListItem, VBtn, VImg, VListItemSubtitle, VDivider } from 'vuetify/components';
const props = defineProps({
alpr: {
@@ -68,24 +63,35 @@ const props = defineProps({
}
});
const isFaceRecognition = computed(() => props.alpr.tags.brand === 'Avigilon');
const manufacturer = computed(() => (
props.alpr.tags.manufacturer || props.alpr.tags.brand || 'Unknown'
));
const cardinalDirection = computed(() => {
const direction = props.alpr.tags.direction || props.alpr.tags["camera:direction"];
if (direction === undefined) {
return 'Unspecified Direction';
} else if (direction.includes(';')) {
return 'Faces Multiple Directions';
} else {
return /^\d+$/.test(direction) ? degreesToCardinal(parseInt(direction)) : direction;
const transparencyLink = computed(() => {
// XXX: eventually get this from /api/operator-info?wikidata=Q1234
return `https://transparency.flocksafety.com/boulder-co-pd`;
});
const abbreviatedOperator = computed(() => {
if (props.alpr.tags.operator === undefined) {
return 'Unknown';
}
}
);
function degreesToCardinal(degrees: number): string {
const cardinals = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NE'];
return 'Faces ' + cardinals[Math.round(degrees / 45) % 8];
}
const replacements: Record<string, string> = {
"Police Department": "PD",
"Sheriff's Office": "SO",
"Sheriffs Office": "SO",
// TODO: maybe include HOAs
};
const operator = props.alpr.tags.operator;
for (const [full, abbr] of Object.entries(replacements)) {
if (operator.includes(full)) {
return operator.replace(full, abbr);
}
}
return operator;
});
function osmNodeLink(id: string): string {
return `https://www.openstreetmap.org/node/${id}`;
+1 -1
View File
@@ -74,7 +74,7 @@ const currentYear = new Date().getFullYear();
const internalLinks = [
{ title: 'About', to: '/about', icon: 'mdi-information' },
{ title: 'Privacy Policy', to: '/privacy', icon: 'mdi-shield' },
{ title: 'Privacy Policy', to: '/privacy', icon: 'mdi-shield-lock' },
{ title: 'Terms of Service', to: '/terms', icon: 'mdi-file-document' },
{ title: 'Contact', to: '/contact', icon: 'mdi-email' },
];