mirror of
https://github.com/FoggedLens/deflock.git
synced 2026-07-06 04:18:00 +02:00
Minor Improvements (#24)
* remove dangers page, fix counter * fix bad instrucitons * cleanup * allow nonstandard direction tag, link to OSM * update privacy policy to account for intro message
This commit is contained in:
@@ -16,7 +16,6 @@ const items = [
|
||||
{ title: 'Home', icon: 'mdi-home', to: '/' },
|
||||
{ title: 'Map', icon: 'mdi-map', to: '/map' },
|
||||
{ title: 'What is an ALPR?', icon: 'mdi-cctv', to: '/what-is-an-alpr' },
|
||||
{ title: 'Dangers of ALPRs', icon: 'mdi-shield-alert', to: '/dangers' },
|
||||
{ title: 'Report an ALPR', icon: 'mdi-map-marker-plus', to: '/report' },
|
||||
{ title: 'Known Operators', icon: 'mdi-police-badge', to: '/operators' },
|
||||
// { title: 'About', icon: 'mdi-information', to: '/about' },
|
||||
|
||||
@@ -32,6 +32,10 @@ p {
|
||||
font-family: "PT Serif", serif;
|
||||
}
|
||||
|
||||
.sans-serif {
|
||||
font-family: unset !important;
|
||||
}
|
||||
|
||||
/* Prevent Scrolling Horizontally */
|
||||
html, body {
|
||||
overflow-x: hidden;
|
||||
|
||||
@@ -12,6 +12,13 @@ import { useDisplay } from 'vuetify'
|
||||
import { getALPRCounts } from '@/services/apiService';
|
||||
import { CountUp } from 'countup.js';
|
||||
|
||||
const props = defineProps({
|
||||
delayMs: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
}
|
||||
});
|
||||
|
||||
const counterEl: Ref<HTMLElement|null> = ref(null);
|
||||
const countupOptions = {
|
||||
useEasing: true,
|
||||
@@ -33,13 +40,17 @@ const counts: Ref<Counts> = ref({
|
||||
const showFinalAnimation = ref(false);
|
||||
const { xs: isMobile } = useDisplay();
|
||||
|
||||
let timeOfMount: number|undefined = undefined;
|
||||
|
||||
onMounted(() => {
|
||||
getALPRCounts().then((response) => {
|
||||
counts.value = response;
|
||||
timeOfMount = new Date().getTime();
|
||||
getALPRCounts().then((countResponse) => {
|
||||
counts.value = countResponse;
|
||||
countUp(countResponse);
|
||||
});
|
||||
});
|
||||
|
||||
watch(counts, (newCounts: Counts) => {
|
||||
function countUp(newCounts: Counts) {
|
||||
if (!newCounts.worldwide) return;
|
||||
if (!counterEl.value) {
|
||||
console.error('Counter element not found');
|
||||
@@ -48,14 +59,23 @@ watch(counts, (newCounts: Counts) => {
|
||||
|
||||
if (!counter) {
|
||||
counter = new CountUp(counterEl.value, newCounts.worldwide, countupOptions);
|
||||
setTimeout(() => {
|
||||
counter?.start();
|
||||
}, 500);
|
||||
|
||||
if (timeOfMount) {
|
||||
const timeSinceMount = new Date().getTime() - timeOfMount;
|
||||
if (timeSinceMount < props.delayMs) {
|
||||
setTimeout(() => {
|
||||
counter?.start();
|
||||
}, props.delayMs - timeSinceMount);
|
||||
} else {
|
||||
counter.start();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
showFinalAnimation.value = true;
|
||||
}, 3000);
|
||||
}, 2500);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{{ alpr.tags.brand }}
|
||||
</span>
|
||||
<span v-else>
|
||||
Unknown Manufacturer
|
||||
Unspecified Manufacturer
|
||||
</span>
|
||||
</b>
|
||||
</v-list-item>
|
||||
@@ -32,19 +32,14 @@
|
||||
{{ alpr.tags.operator }}
|
||||
</span>
|
||||
<span v-else>
|
||||
Unknown Operator
|
||||
Unspecified Operator
|
||||
</span>
|
||||
</b>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<div class="text-center text-grey-darken-1">
|
||||
|
||||
<v-tooltip open-delay="500" text="OSM Node ID" location="bottom">
|
||||
<template #activator="{ props }">
|
||||
<span style="font-size: 0.9em; cursor: default" v-bind="props">node/{{ alpr.id }}</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<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>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</template>
|
||||
@@ -53,7 +48,7 @@
|
||||
import { defineProps, computed } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import type { ALPR } from '@/types';
|
||||
import { VIcon, VList, VSheet, VListItem, VTooltip } from 'vuetify/components';
|
||||
import { VIcon, VList, VSheet, VListItem, VBtn } from 'vuetify/components';
|
||||
|
||||
const props = defineProps({
|
||||
alpr: {
|
||||
@@ -64,12 +59,18 @@ const props = defineProps({
|
||||
|
||||
const isFaceRecognition = computed(() => props.alpr.tags.brand === 'Avigilon');
|
||||
|
||||
const cardinalDirection = computed(() =>
|
||||
props.alpr.tags.direction === undefined ? 'Unknown Direction' : degreesToCardinal(parseInt(props.alpr.tags.direction))
|
||||
const cardinalDirection = computed(() => {
|
||||
const direction = props.alpr.tags.direction || props.alpr.tags["camera:direction"];
|
||||
return direction === undefined ? 'Unspecified Direction' : degreesToCardinal(parseInt(direction))
|
||||
}
|
||||
);
|
||||
|
||||
function degreesToCardinal(degrees: number): string {
|
||||
const cardinals = ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest'];
|
||||
return 'Faces ' + cardinals[Math.round(degrees / 45) % 8];
|
||||
}
|
||||
|
||||
function osmNodeLink(id: string): string {
|
||||
return `https://www.openstreetmap.org/node/${id}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -56,7 +56,7 @@ let currentLocationLayer: FeatureGroup;
|
||||
|
||||
// Marker Creation Utilities
|
||||
function createSVGMarker(alpr: ALPR): string {
|
||||
const orientationDegrees = alpr.tags.direction;
|
||||
const orientationDegrees = alpr.tags.direction || alpr.tags['camera:direction'];
|
||||
const fovPath = `
|
||||
<path class="someSVGpath" d="M215.248,221.461L99.696,43.732C144.935,16.031 198.536,0 256,0C313.464,0 367.065,16.031 412.304,43.732L296.752,221.461C287.138,209.593 272.448,202.001 256,202.001C239.552,202.001 224.862,209.593 215.248,221.461Z" style="fill:rgb(87,87,87);fill-opacity:0.46;"/>
|
||||
<path class="someSVGpath" d="M215.248,221.461L99.696,43.732C144.935,16.031 198.536,0 256,0C313.464,0 367.065,16.031 412.304,43.732L296.752,221.461C287.138,209.593 272.448,202.001 256,202.001C239.552,202.001 224.862,209.593 215.248,221.461ZM217.92,200.242C228.694,192.652 241.831,188.195 256,188.195C270.169,188.195 283.306,192.652 294.08,200.242C294.08,200.242 392.803,48.4 392.803,48.4C352.363,26.364 305.694,13.806 256,13.806C206.306,13.806 159.637,26.364 119.197,48.4L217.92,200.242Z" style="fill:rgb(137,135,135);"/>
|
||||
@@ -73,7 +73,7 @@ function createSVGMarker(alpr: ALPR): string {
|
||||
}
|
||||
|
||||
function createMarker(alpr: ALPR): Marker | CircleMarker {
|
||||
if (hasPlottableOrientation(alpr.tags.direction)) {
|
||||
if (hasPlottableOrientation(alpr.tags.direction || alpr.tags['camera:direction'])) {
|
||||
const icon = L.divIcon({
|
||||
className: 'leaflet-data-marker',
|
||||
html: createSVGMarker(alpr),
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<v-dialog v-model="show" max-width="900">
|
||||
<v-card>
|
||||
<v-card-title class="text-center py-4 font-weight-bold">
|
||||
<h3 class="headline">Welcome to DeFlock</h3>
|
||||
</v-card-title>
|
||||
<p class="mx-8 text-center">
|
||||
DeFlock is powered by <b>crowdsourced data</b> from the OpenStreetMap community.
|
||||
</p>
|
||||
|
||||
<v-divider class="mx-4 mt-4" />
|
||||
|
||||
<v-list class="text-center">
|
||||
<v-list-item class="my-4">
|
||||
<v-icon size="x-large" color="primary" class="mb-2">mdi-progress-pencil</v-icon>
|
||||
<v-list-item-title class="font-weight-bold">The map is incomplete!</v-list-item-title>
|
||||
<v-list-item-subtitle>The ALPRs displayed here are a starting point, and new locations are constantly being added.</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<div class="small text-grey-darken-2 text-center mx-4 mb-2">By using this site, you agree to our <router-link to="/terms">Terms of Service</router-link>.</div>
|
||||
<v-card-actions>
|
||||
<v-btn flat class="w-100" size="x-large" color="rgb(18, 151, 195)" variant="elevated" @click="acknowledge">Got it</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
if (!localStorage.getItem('acknowledged')) {
|
||||
show.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
function acknowledge() {
|
||||
show.value = false;
|
||||
localStorage.setItem('acknowledged', 'true');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.no-small {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-row class="align-center justify-center my-4">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-col cols="12" sm="6" class="text-center">
|
||||
<v-select
|
||||
color="rgb(18, 151, 195)"
|
||||
prepend-inner-icon="mdi-factory"
|
||||
@@ -22,6 +22,7 @@
|
||||
max-width="100%"
|
||||
class="my-4"
|
||||
></v-img>
|
||||
<v-btn to="/what-is-an-alpr#photos" color="grey-darken-2" variant="text" size="small"><v-icon start>mdi-image-multiple</v-icon> See All Photos</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6">
|
||||
@@ -56,25 +57,25 @@ const alprBrands: WikidataItem[] = [
|
||||
name: 'Flock Safety',
|
||||
nickname: 'Flock',
|
||||
wikidata: 'Q108485435',
|
||||
exampleImage: '/flock-1.jpg',
|
||||
exampleImage: '/alprs/flock-1.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Motorola Solutions',
|
||||
nickname: 'Motorola/Vigilant',
|
||||
wikidata: 'Q634815',
|
||||
exampleImage: '/vigilant-1.jpg',
|
||||
exampleImage: '/alprs/motorola-4.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Leonardo',
|
||||
nickname: 'Leonardo/ELSAG',
|
||||
wikidata: 'Q910379',
|
||||
exampleImage: '/elsag.webp',
|
||||
exampleImage: '/alprs/elsag-1.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Neology, Inc.',
|
||||
nickname: 'Neology',
|
||||
wikidata: 'Q130958232',
|
||||
exampleImage: '/neology-1.jpg',
|
||||
exampleImage: '/alprs/neology-2.jpg',
|
||||
},
|
||||
{
|
||||
name: '(brand goes here)',
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<div>
|
||||
<p>© {{ currentYear }} DeFlock. All Rights Reserved</p>
|
||||
<p>Map data © <a href="https://www.openstreetmap.org/copyright" target="_blank" style="color: unset; font-weight: normal;">OpenStreetMap contributors</a></p>
|
||||
<p class="mt-4">v1.0</p>
|
||||
<p class="mt-4">v1.0.1</p>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -114,14 +114,6 @@ const router = createRouter({
|
||||
title: 'Donate | DeFlock'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/dangers',
|
||||
name: 'dangers',
|
||||
component: () => import('../views/Dangers.vue'),
|
||||
meta: {
|
||||
title: 'ALPR Dangers | DeFlock'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<template>
|
||||
<v-container class="info-section mb-16">
|
||||
<!-- Hero Section -->
|
||||
<v-row justify="center" class="hero-section-harms text-center mb-4">
|
||||
<v-col cols="12" md="8">
|
||||
<h1 class="mb-4">The Dangers of ALPRs</h1>
|
||||
<p class="mb-4 font-weight-bold bigger">
|
||||
ALPRs promise safety but deliver risks to privacy and civil rights.
|
||||
</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="mb-4">
|
||||
<v-col cols="12" class="bigger">
|
||||
<p class="mb-4">
|
||||
Thousands of police departments, homeowners associations, and businesses across the country deploy automated license plate readers (ALPRs) with the expectation that these surveillance tools can reduce crime.
|
||||
</p>
|
||||
<p>
|
||||
In reality, ALPRs pose a real threat to the civil rights, liberties, and safety of the communities of people under surveillance.
|
||||
</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<dangers showAll />
|
||||
</v-container>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Dangers from '@/components/Dangers.vue';
|
||||
import Footer from '@/components/layout/Footer.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hero-section-harms {
|
||||
background: url('/chicago-pd.jpg') no-repeat center center;
|
||||
background-size: cover;
|
||||
color: white;
|
||||
padding: 100px 0 !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-section-harms::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-section-harms > * {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<NewVisitor />
|
||||
<div class="map-container" @keyup="handleKeyUp">
|
||||
<leaflet-map
|
||||
v-if="center"
|
||||
@@ -56,15 +57,14 @@ import { geocodeQuery } from '@/services/apiService';
|
||||
import { useDisplay, useTheme } from 'vuetify';
|
||||
import { useGlobalStore } from '@/stores/global';
|
||||
import { useTilesStore } from '@/stores/tiles';
|
||||
import type { ALPR } from '@/types';
|
||||
import L from 'leaflet';
|
||||
globalThis.L = L;
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
import LeafletMap from '@/components/LeafletMap.vue';
|
||||
import NewVisitor from '@/components/NewVisitor.vue';
|
||||
|
||||
const DEFAULT_ZOOM = 12;
|
||||
|
||||
const theme = useTheme();
|
||||
const zoom: Ref<number> = ref(DEFAULT_ZOOM);
|
||||
const center: Ref<any|null> = ref(null);
|
||||
const bounds: Ref<BoundingBox|null> = ref(null);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<h2>Information We Collect</h2>
|
||||
<p>
|
||||
DeFlock does not collect, store, or process any personal information about our users. We do not use cookies, analytics, or tracking technologies on our website.
|
||||
DeFlock does not collect, store, or process any personal information about our users. We use local storage in your browser to anonymously identify first-time visitors for the purpose of showing an introductory message. This data cannot be used to identify you personally. We do not use cookies, analytics, or tracking technologies on our website.
|
||||
</p>
|
||||
|
||||
<h2>Third-Party Services</h2>
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
editable
|
||||
>
|
||||
<p>
|
||||
Once you've added the ALPR to the map, click the <strong>Save</strong> button in the top left corner of the editor. You'll be asked to provide a brief description of your changes. Once you've submitted your changes, the ALPR will be added to OpenStreetMap.
|
||||
Once you've added the ALPR to the map, click the <strong>Save</strong> button in the top right corner of the editor. You'll be asked to provide a brief description of your changes. Once you've submitted your changes, the ALPR will be added to OpenStreetMap.
|
||||
</p>
|
||||
<v-alert
|
||||
variant="tonal"
|
||||
@@ -117,15 +117,10 @@
|
||||
</template>
|
||||
</v-stepper-vertical>
|
||||
|
||||
<h2 class="text-center mt-16">Edit an Existing ALPR</h2>
|
||||
<p>
|
||||
If you find an ALPR that's missing information and would like to update it, you can follow the same steps as above. Each ALPR on DeFlock has a Node ID that you can use to find it on OpenStreetMap.
|
||||
</p>
|
||||
|
||||
<h2 class="text-center">Edit an Existing ALPR</h2>
|
||||
<p class="mb-16">
|
||||
Simply click on the ALPR with missing information, and find the Node ID (e.g. <code>node/1237489334</code>) at the bottom of the popup. In the OSM editor search field, paste the <i>numerical portion</i> of the Node ID to find the ALPR and make your changes.
|
||||
If you find an ALPR that's missing information and would like to update it, you can click the <b>View on OSM</b> button to edit it in the OpenStreetMap editor.
|
||||
</p>
|
||||
|
||||
</v-container>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
@@ -17,43 +17,20 @@
|
||||
ALPRs invade your privacy and violate your civil liberties. Here's how:
|
||||
</p>
|
||||
|
||||
<dangers />
|
||||
<Dangers />
|
||||
|
||||
<h2>What They Look Like</h2>
|
||||
<h2 id="photos">Photos by Vendor</h2>
|
||||
|
||||
<v-alert
|
||||
density="compact"
|
||||
class="mb-6 text-center"
|
||||
variant="tonal"
|
||||
color="rgb(18, 151, 195)"
|
||||
>
|
||||
<p>
|
||||
<v-icon v-if="isMobile" size="x-large">mdi-gesture-tap</v-icon>
|
||||
<v-icon v-else size="x-large">mdi-button-cursor</v-icon>
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ isMobile ? 'Tap' : 'Hover over' }}</b> an image below to identify the make.
|
||||
<span v-if="!isMobile"><b>Click</b> an image to enlarge.</span>
|
||||
</p>
|
||||
</v-alert>
|
||||
|
||||
<v-row>
|
||||
<v-col v-for="image in images" cols="12" sm="6" md="4">
|
||||
<v-hover>
|
||||
<template v-slot:default="{ isHovering, props }">
|
||||
<v-img style="cursor: pointer;" @click="openFullScreenImage(image)" v-bind="props" cover :aspect-ratio="3/2" :src="image.url">
|
||||
<transition name="fade">
|
||||
<div class="scrim" v-show="isHovering">
|
||||
<span class="scrim-text">{{ image.brand }}</span>
|
||||
</div>
|
||||
</transition>
|
||||
</v-img>
|
||||
</template>
|
||||
</v-hover>
|
||||
<v-row v-for="vendor in vendors" :key="vendor.vendor" class="mb-4">
|
||||
<v-col cols="12">
|
||||
<h3 class="text-center">{{ vendor.vendor }}</h3>
|
||||
</v-col>
|
||||
<v-col v-for="url in vendor.imageUrls" cols="12" sm="6" md="4">
|
||||
<v-img @click="openImageInNewTab(url)" style="cursor: pointer;" cover :aspect-ratio="3/2" :src="url" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<h2>Common Brands of ALPRs</h2>
|
||||
<h2>Common Vendors</h2>
|
||||
<ul class="serif mb-16">
|
||||
<li>
|
||||
<a href="https://www.flocksafety.com/devices/lpr" target="_blank">Flock Safety</a> - A leading provider of ALPR technology, known for their solar-powered cameras. This is the most common brand of ALPR in the US. Flock Safety cameras are used by police departments, HOAs, as well as private businesses such as hardware stores and hotels. One of the most appealing features of Flock cameras is the data sharing network, which allows law enforcement agencies to access data from other Flock cameras in the area. This means that even if your local police department doesn't have a Flock camera, they can still access data from other Flock cameras in the area.
|
||||
@@ -69,54 +46,52 @@
|
||||
</li>
|
||||
</ul>
|
||||
</v-container>
|
||||
|
||||
<v-dialog class="full-screen-image" v-model="showFullScreenImage">
|
||||
<v-card style="overflow: hidden;">
|
||||
<v-btn size="x-large" class="image-close-btn" flat icon @click="showFullScreenImage = false" color="transparent">
|
||||
<v-icon :color="fullScreenImage.useDarkCloseButton ? 'black' : 'white'">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<v-img v-if="fullScreenImage" cover :aspect-ratio="3/2" :src="fullScreenImage.url" />
|
||||
</v-card>
|
||||
|
||||
</v-dialog>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ref, type Ref } from 'vue';
|
||||
import { useDisplay } from 'vuetify';
|
||||
import Dangers from '@/components/Dangers.vue';
|
||||
import Footer from '@/components/layout/Footer.vue';
|
||||
const route = useRoute();
|
||||
const { xs: isMobile } = useDisplay();
|
||||
|
||||
const flockImageCount = 6;
|
||||
const vigilantImageCount = 3;
|
||||
|
||||
const showFullScreenImage = ref(false);
|
||||
const fullScreenImage: Ref<any|undefined> = ref(undefined);
|
||||
|
||||
function openFullScreenImage(image: object) {
|
||||
if (isMobile.value)
|
||||
return;
|
||||
fullScreenImage.value = image;
|
||||
showFullScreenImage.value = true;
|
||||
function openImageInNewTab(url: string, newTab: boolean = true) {
|
||||
window.open(url, newTab ? '_blank' : '_self');
|
||||
}
|
||||
|
||||
const images = [
|
||||
...Array.from({ length: flockImageCount }, (_, i) => ({
|
||||
url: `/flock-${i + 1}.jpg`,
|
||||
brand: 'flock',
|
||||
useDarkCloseButton: false,
|
||||
})),
|
||||
...Array.from({ length: vigilantImageCount }, (_, i) => ({
|
||||
url: `/vigilant-${i + 1}.jpg`,
|
||||
brand: 'motorola',
|
||||
useDarkCloseButton: true,
|
||||
}))
|
||||
];
|
||||
const vendors = [
|
||||
{
|
||||
vendor: 'Flock',
|
||||
count: 4,
|
||||
urlScheme: '/alprs/flock-{index}.jpg',
|
||||
},
|
||||
{
|
||||
vendor: 'Motorola/Vigilant',
|
||||
count: 4,
|
||||
urlScheme: '/alprs/motorola-{index}.jpg',
|
||||
},
|
||||
{
|
||||
vendor: 'Leonardo/ELSAG',
|
||||
count: 4,
|
||||
urlScheme: '/alprs/elsag-{index}.jpg',
|
||||
},
|
||||
{
|
||||
vendor: 'Neology',
|
||||
count: 2,
|
||||
urlScheme: '/alprs/neology-{index}.jpg',
|
||||
},
|
||||
].reduce((acc: any, vendor: any) => {
|
||||
const imageUrls = Array.from({ length: vendor.count }, (_, index) =>
|
||||
vendor.urlScheme.replace('{index}', String(index + 1)),
|
||||
);
|
||||
|
||||
acc.push({ vendor: vendor.vendor, imageUrls });
|
||||
return acc;
|
||||
}, []);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user