mirror of
https://github.com/FoggedLens/deflock.git
synced 2026-02-12 15:02:45 +00:00
use iconify
This commit is contained in:
49
webapp/package-lock.json
generated
49
webapp/package-lock.json
generated
@@ -8,6 +8,8 @@
|
||||
"name": "deflock",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@iconify-vue/ic": "^1.0.1",
|
||||
"@iconify-vue/mdi": "^1.0.1",
|
||||
"@types/leaflet.markercluster": "^1.5.5",
|
||||
"@unhead/vue": "^1.11.14",
|
||||
"axios": "^1.7.7",
|
||||
@@ -19,7 +21,6 @@
|
||||
"vuetify": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/leaflet": "^1.9.15",
|
||||
"@types/node": "^20.14.5",
|
||||
@@ -520,19 +521,51 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify-vue/ic": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@iconify-vue/ic/-/ic-1.0.1.tgz",
|
||||
"integrity": "sha512-Lbe8rstZhBx8LSgsaTFaui+rGZT1Rt0rXraKFivPsXB3E7orpIaucoMUEb8Wgy9l1KgGNm2bUVe2s5wC4qnr7A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@iconify/css-vue": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify-vue/mdi": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@iconify-vue/mdi/-/mdi-1.0.1.tgz",
|
||||
"integrity": "sha512-A6RLb4YmyjH9xbTtX74YOco8p1QpGlBwcvIAZOCHvVyCoMIIcbvVOnMak3dhqoFaepjkqhiCnqoRn8yPu/FMhg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@iconify/css-vue": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/css-vue": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/css-vue/-/css-vue-1.0.1.tgz",
|
||||
"integrity": "sha512-rGkUIToUFUfP1zIYrY8A1pWUcadGxbMgAsUyI4PmK6BgxTO5Sajw1cbl6gqMi/D26S6LSjEG/Q+7O7gcLJthaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/cyberalien"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/types": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mdi/font": {
|
||||
"version": "7.4.47",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz",
|
||||
"integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.50",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz",
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
"type-check": "vue-tsc --build --force"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-vue/ic": "^1.0.1",
|
||||
"@iconify-vue/mdi": "^1.0.1",
|
||||
"@types/leaflet.markercluster": "^1.5.5",
|
||||
"@unhead/vue": "^1.11.14",
|
||||
"axios": "^1.7.7",
|
||||
@@ -23,7 +25,6 @@
|
||||
"vuetify": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/leaflet": "^1.9.15",
|
||||
"@types/node": "^20.14.5",
|
||||
|
||||
@@ -5,6 +5,22 @@ import { useTheme } from 'vuetify';
|
||||
import DiscordWarningDialog from '@/components/DiscordWarningDialog.vue';
|
||||
import { useDiscordIntercept } from '@/composables/useDiscordIntercept';
|
||||
|
||||
// Icons
|
||||
import HamburgerIcon from '@iconify-vue/mdi/menu';
|
||||
import HomeIcon from '@iconify-vue/mdi/home';
|
||||
import MapIcon from '@iconify-vue/mdi/map';
|
||||
import SchoolIcon from '@iconify-vue/mdi/school';
|
||||
import ShoppingIcon from '@iconify-vue/mdi/shopping-cart';
|
||||
import MapMarkerPlusIcon from '@iconify-vue/mdi/map-marker-plus';
|
||||
import FileDocumentIcon from '@iconify-vue/mdi/file-document';
|
||||
import AccountVoiceIcon from '@iconify-vue/mdi/account-voice';
|
||||
import EmailOutlineIcon from '@iconify-vue/mdi/email-outline';
|
||||
import GithubIcon from '@iconify-vue/mdi/github';
|
||||
import HeartIcon from '@iconify-vue/mdi/heart';
|
||||
import DiscordIcon from '@iconify-vue/ic/baseline-discord';
|
||||
import ChevronDownIcon from '@iconify-vue/mdi/chevron-down';
|
||||
import ThemeIcon from '@iconify-vue/mdi/theme-light-dark';
|
||||
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const isDark = computed(() => theme.name.value === 'dark');
|
||||
@@ -34,23 +50,23 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const items = [
|
||||
{ title: 'Home', icon: 'mdi-home', to: '/' },
|
||||
{ title: 'Map', icon: 'mdi-map', to: '/map' },
|
||||
{ title: 'Learn', icon: 'mdi-school', to: '/what-is-an-alpr' },
|
||||
{ title: 'Store', icon: 'mdi-shopping', to: '/store' },
|
||||
{ title: 'Home', icon: HomeIcon, to: '/' },
|
||||
{ title: 'Map', icon: MapIcon, to: '/map' },
|
||||
{ title: 'Learn', icon: SchoolIcon, to: '/what-is-an-alpr' },
|
||||
{ title: 'Store', icon: ShoppingIcon, to: '/store' },
|
||||
]
|
||||
|
||||
const contributeItems = [
|
||||
{ title: 'Submit Cameras', icon: 'mdi-map-marker-plus', to: '/report' },
|
||||
{ title: 'Public Records', icon: 'mdi-file-document', to: '/foia' },
|
||||
{ title: 'City Council', icon: 'mdi-account-voice', to: '/council' },
|
||||
{ title: 'Submit Cameras', icon: MapMarkerPlusIcon, to: '/report' },
|
||||
{ title: 'Public Records', icon: FileDocumentIcon, to: '/foia' },
|
||||
{ title: 'City Council', icon: AccountVoiceIcon, to: '/council' },
|
||||
]
|
||||
|
||||
const metaItems = [
|
||||
{ title: 'Discord', customIcon: '/icon-discord.svg', customIconDark: '/icon-discord-white.svg', customIconGrey: '/icon-discord-grey.svg', href: 'https://discord.gg/aV7v4R3sKT'},
|
||||
{ title: 'Contact', icon: 'mdi-email-outline', to: '/contact' },
|
||||
{ title: 'GitHub', icon: 'mdi-github', href: 'https://github.com/frillweeman/deflock'},
|
||||
{ title: 'Donate', icon: 'mdi-heart', to: '/donate'},
|
||||
{ title: 'Discord', icon: DiscordIcon, href: 'https://discord.gg/aV7v4R3sKT'},
|
||||
{ title: 'Contact', icon: EmailOutlineIcon, to: '/contact' },
|
||||
{ title: 'GitHub', icon: GithubIcon, href: 'https://github.com/frillweeman/deflock'},
|
||||
{ title: 'Donate', icon: HeartIcon, to: '/donate'},
|
||||
];
|
||||
const drawer = ref(false)
|
||||
|
||||
@@ -79,6 +95,7 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
@click.stop="drawer = !drawer"
|
||||
class="d-md-none"
|
||||
aria-label="Toggle Navigation Drawer"
|
||||
:icon="HamburgerIcon"
|
||||
></v-app-bar-nav-icon>
|
||||
|
||||
<!-- Logo -->
|
||||
@@ -114,7 +131,7 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
<v-btn
|
||||
variant="text"
|
||||
v-bind="props"
|
||||
append-icon="mdi-chevron-down"
|
||||
:append-icon="ChevronDownIcon"
|
||||
class="mx-1"
|
||||
>
|
||||
Contribute
|
||||
@@ -128,7 +145,9 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
link
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon>{{ item.icon }}</v-icon>
|
||||
<v-icon>
|
||||
<component :is="item.icon" class="custom-icon" />
|
||||
</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
@@ -141,7 +160,7 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
<v-btn
|
||||
variant="text"
|
||||
v-bind="props"
|
||||
append-icon="mdi-chevron-down"
|
||||
:append-icon="ChevronDownIcon"
|
||||
class="mx-1"
|
||||
>
|
||||
Get Involved
|
||||
@@ -157,15 +176,9 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
link
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon v-if="item.icon">{{ item.icon }}</v-icon>
|
||||
<v-img
|
||||
v-else-if="item.customIcon"
|
||||
class="mr-8"
|
||||
contain
|
||||
width="24"
|
||||
height="24"
|
||||
:src="isDark ? item.customIconDark : item.customIconGrey"
|
||||
/>
|
||||
<v-icon>
|
||||
<component :is="item.icon" />
|
||||
</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
@@ -176,8 +189,10 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
|
||||
<v-spacer class="d-md-none" />
|
||||
|
||||
<v-btn icon>
|
||||
<v-icon @click="toggleTheme" aria-label="Toggle Theme">mdi-theme-light-dark</v-icon>
|
||||
<v-btn icon @click="toggleTheme" aria-label="Toggle Theme">
|
||||
<v-icon>
|
||||
<ThemeIcon />
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-app-bar>
|
||||
|
||||
@@ -196,7 +211,9 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
:to="item.to"
|
||||
role="option"
|
||||
>
|
||||
<v-icon start>{{ item.icon }}</v-icon>
|
||||
<v-icon start>
|
||||
<component :is="item.icon" />
|
||||
</v-icon>
|
||||
{{ item.title }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -212,7 +229,9 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
:to="item.to"
|
||||
role="option"
|
||||
>
|
||||
<v-icon v-if="item.icon" start>{{ item.icon }}</v-icon>
|
||||
<v-icon start>
|
||||
<component :is="item.icon" />
|
||||
</v-icon>
|
||||
<span style="vertical-align: middle;">{{ item.title }}</span>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -230,16 +249,9 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
:target="item.href ? '_blank' : undefined"
|
||||
role="option"
|
||||
>
|
||||
<v-icon v-if="item.icon" start>{{ item.icon }}</v-icon>
|
||||
<v-img
|
||||
v-else-if="item.customIcon"
|
||||
class="mr-2 custom-icon"
|
||||
contain
|
||||
width="24"
|
||||
height="24"
|
||||
:src="isDark ? item.customIconDark : item.customIcon"
|
||||
style="vertical-align: middle;"
|
||||
/>
|
||||
<v-icon start>
|
||||
<component :is="item.icon" />
|
||||
</v-icon>
|
||||
<span style="vertical-align: middle;">{{ item.title }}</span>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -257,10 +269,3 @@ watch(() => theme.global.name.value, (newTheme) => {
|
||||
/>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.custom-icon {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,16 +8,22 @@
|
||||
>
|
||||
<v-card class="h-100 d-flex flex-column">
|
||||
<v-card-title class="text-center py-4 font-weight-bold bg-warning d-flex align-center justify-center">
|
||||
<v-icon icon="mdi-alert-circle" size="large" class="mr-2"></v-icon>
|
||||
<v-icon size="large" class="mr-2">
|
||||
<AlertCircleIcon />
|
||||
</v-icon>
|
||||
<h3 class="headline">Are you sure it's an ALPR?</h3>
|
||||
<v-spacer v-if="$vuetify.display.mobile"></v-spacer>
|
||||
<v-btn
|
||||
icon
|
||||
v-if="$vuetify.display.mobile"
|
||||
icon="mdi-close"
|
||||
variant="text"
|
||||
color="on-warning"
|
||||
@click="dismiss"
|
||||
></v-btn>
|
||||
>
|
||||
<v-icon>
|
||||
<CloseIcon />
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-6 flex-grow-1 d-flex flex-column justify-center">
|
||||
@@ -43,10 +49,14 @@
|
||||
variant="elevated"
|
||||
size="large"
|
||||
to="/identify"
|
||||
prepend-icon="mdi-image-search"
|
||||
class="mb-3"
|
||||
@click="dismiss"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<ImageSearchIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
View ALPR Gallery
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -71,6 +81,10 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import AlertCircleIcon from '@iconify-vue/mdi/alert-circle';
|
||||
import CloseIcon from '@iconify-vue/mdi/close';
|
||||
import ImageSearchIcon from '@iconify-vue/mdi/image-search';
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="datasets"
|
||||
hide-default-footer
|
||||
:sort-by="[ { key: 'state', order: 'asc' } ]"
|
||||
>
|
||||
<template v-slot:item.author="i: any">
|
||||
<a v-if="i.item.authorUrl" :href="i.item.authorUrl" target="_blank">{{ i.item.author }}</a>
|
||||
<span v-else>{{ i.item.author }}</span>
|
||||
</template>
|
||||
<template v-slot:item.url="i: any">
|
||||
<v-btn variant="text" color="primary" :href="i.item.url" target="_blank" :disabled="!i.item.url">
|
||||
<v-icon start>mdi-download</v-icon>
|
||||
<span>Download ({{ i.item.type }})</span>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const datasets = [
|
||||
{
|
||||
title: 'Wi-Fi Hits',
|
||||
author: 'Ryan O\'Horo',
|
||||
authorUrl: 'https://www.ryanohoro.com/post/spotting-flock-safety-s-falcon-cameras',
|
||||
url: 'https://deflock-clusters.s3.us-east-1.amazonaws.com/Flock-_______20240530_124303.csv',
|
||||
description: 'Crowdsourced Wi-Fi hits from Flock Safety cameras',
|
||||
type: 'csv',
|
||||
},
|
||||
{
|
||||
title: 'External Battery (Bluetooth) Hits - Flock',
|
||||
author: 'Ryan O\'Horo',
|
||||
authorUrl: 'https://www.ryanohoro.com/post/spotting-flock-safety-s-falcon-cameras',
|
||||
url: 'https://deflock-clusters.s3.us-east-1.amazonaws.com/FS+Ext+Battery_20240530_105846.csv',
|
||||
description: 'Crowdsourced Bluetooth hits from Flock Safety cameras with "Flock" radio name',
|
||||
type: 'csv',
|
||||
},
|
||||
{
|
||||
title: 'External Battery (Bluetooth) Hits - Penguin',
|
||||
author: 'Ryan O\'Horo',
|
||||
authorUrl: 'https://www.ryanohoro.com/post/spotting-flock-safety-s-falcon-cameras',
|
||||
url: 'https://deflock-clusters.s3.us-east-1.amazonaws.com/Penguin-___________20240530_111436.csv',
|
||||
description: 'Crowdsourced Bluetooth hits from Flock Safety cameras with "Penguin" radio name',
|
||||
type: 'csv',
|
||||
},
|
||||
{
|
||||
title: 'Atlanta, GA ALPRs',
|
||||
author: 'veroniquedra',
|
||||
authorUrl: null,
|
||||
url: 'https://deflock-clusters.s3.us-east-1.amazonaws.com/maximum_dots.csv',
|
||||
description: 'Over 2,000 cameras in the Atlanta area',
|
||||
type: 'csv',
|
||||
},
|
||||
{
|
||||
title: 'Akron, Ohio ALPRs',
|
||||
author: 'organizers of Akron Ohio',
|
||||
authorUrl: null,
|
||||
url: 'https://deflock-clusters.s3.us-east-1.amazonaws.com/Pigvision.csv',
|
||||
description: 'Crowdsourced Bluetooth hits from Flock Safety cameras with "Penguin" radio name',
|
||||
type: 'csv',
|
||||
},
|
||||
];
|
||||
|
||||
const headers = [
|
||||
{ title: 'Title', value: 'title', sortable: false },
|
||||
{ title: 'Author', value: 'author', sortable: false },
|
||||
{ title: 'Description', value: 'description', sortable: false },
|
||||
{ title: '', value: 'url', sortable: false },
|
||||
];
|
||||
</script>
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div style="position: relative">
|
||||
<v-btn v-if="showCopyButton" color="white" @click="copyToClipboard" icon variant="plain" flat class="copy-button">
|
||||
<v-icon class="copy-icon-with-shadow">mdi-content-copy</v-icon>
|
||||
<v-icon class="copy-icon-with-shadow">
|
||||
<CopyIcon />
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<code ref="codeContent">
|
||||
<slot></slot>
|
||||
@@ -13,7 +15,9 @@
|
||||
variant="text"
|
||||
@click="snackbarOpen = false"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
<v-icon>
|
||||
<CloseIcon />
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
@@ -23,6 +27,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import CloseIcon from '@iconify-vue/mdi/close';
|
||||
import CopyIcon from '@iconify-vue/mdi/content-copy';
|
||||
|
||||
defineProps({
|
||||
showCopyButton: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
<v-list density="compact" class="my-2">
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-police-badge"></v-icon>
|
||||
<v-icon>
|
||||
<PoliceBadgeIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
|
||||
<v-list-item-subtitle style="font-size: 1em">
|
||||
@@ -28,7 +30,9 @@
|
||||
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-factory"></v-icon>
|
||||
<v-icon>
|
||||
<FactoryIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
|
||||
<v-list-item-subtitle style="font-size: 1em">
|
||||
@@ -44,7 +48,12 @@
|
||||
</v-list>
|
||||
|
||||
<div class="text-center">
|
||||
<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>
|
||||
<v-btn target="_blank" size="x-small" :href="osmNodeLink(props.alpr.id)" variant="text" color="grey">
|
||||
<v-icon start>
|
||||
<OpenInNewIcon />
|
||||
</v-icon>
|
||||
View on OSM
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</template>
|
||||
@@ -55,6 +64,10 @@ import type { ComputedRef, PropType } from 'vue';
|
||||
import type { ALPR } from '@/types';
|
||||
import { VIcon, VList, VSheet, VListItem, VBtn, VImg, VListItemSubtitle, VDivider } from 'vuetify/components';
|
||||
|
||||
import PoliceBadgeIcon from '@iconify-vue/mdi/police-badge';
|
||||
import FactoryIcon from '@iconify-vue/mdi/factory';
|
||||
import OpenInNewIcon from '@iconify-vue/mdi/open-in-new';
|
||||
|
||||
const props = defineProps({
|
||||
alpr: {
|
||||
type: Object as PropType<ALPR>,
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<v-card class="discord-warning-card">
|
||||
<v-card-text>
|
||||
<div class="discord-warning-content">
|
||||
<v-icon color="warning" size="28" class="mb-2">mdi-alert</v-icon>
|
||||
<v-icon color="warning" size="28" class="mb-2">
|
||||
<AlertIcon />
|
||||
</v-icon>
|
||||
<p class="mb-3 text-body-1">
|
||||
<strong>You're about to join Discord</strong>
|
||||
</p>
|
||||
@@ -17,7 +19,9 @@
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-end pt-0">
|
||||
<v-btn color="primary" @click="proceed" class="mr-2" rounded>
|
||||
<v-icon start>mdi-arrow-right</v-icon>
|
||||
<v-icon start>
|
||||
<ArrowRightIcon />
|
||||
</v-icon>
|
||||
Proceed
|
||||
</v-btn>
|
||||
<v-btn variant="text" @click="cancel" rounded>
|
||||
@@ -29,6 +33,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AlertIcon from '@iconify-vue/mdi/alert';
|
||||
import ArrowRightIcon from '@iconify-vue/mdi/arrow-right';
|
||||
|
||||
const props = defineProps<{ modelValue: boolean; discordUrl: string }>();
|
||||
const emit = defineEmits(['update:modelValue', 'proceed']);
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
<v-card-text class="py-0">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<span>
|
||||
<v-icon size="small" class="mr-2">mdi-chart-bubble</v-icon>
|
||||
<v-icon size="small" class="mr-2">
|
||||
<GroupingIcon />
|
||||
</v-icon>
|
||||
<span class="text-caption mr-2">Grouping</span>
|
||||
</span>
|
||||
<v-switch
|
||||
@@ -35,7 +37,9 @@
|
||||
<v-card-text class="py-0">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<span>
|
||||
<v-icon size="small" class="mr-2">mdi-map-outline</v-icon>
|
||||
<v-icon size="small" class="mr-2">
|
||||
<CityBoundariesIcon />
|
||||
</v-icon>
|
||||
<span class="text-caption mr-2">City Boundaries</span>
|
||||
</span>
|
||||
<v-switch
|
||||
@@ -61,7 +65,9 @@
|
||||
v-if="showAutoDisabledStatus"
|
||||
class="clustering-status-bar"
|
||||
>
|
||||
<v-icon size="small" class="mr-2">mdi-information</v-icon>
|
||||
<v-icon size="small" class="mr-2">
|
||||
<InformationIcon />
|
||||
</v-icon>
|
||||
<span class="text-caption">
|
||||
Camera grouping is on for performance at this zoom level.
|
||||
</span>
|
||||
@@ -73,7 +79,9 @@
|
||||
class="ml-2"
|
||||
@click="dismissZoomWarning"
|
||||
>
|
||||
<v-icon size="small">mdi-close</v-icon>
|
||||
<v-icon size="small">
|
||||
<CloseIcon />
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-slide-y-transition>
|
||||
@@ -94,6 +102,11 @@ import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
|
||||
import 'leaflet.markercluster/dist/MarkerCluster.css';
|
||||
import { useTheme } from 'vuetify';
|
||||
|
||||
import GroupingIcon from '@iconify-vue/ic/baseline-bubble-chart';
|
||||
import CityBoundariesIcon from '@iconify-vue/mdi/map-outline';
|
||||
import InformationIcon from '@iconify-vue/mdi/information-outline';
|
||||
import CloseIcon from '@iconify-vue/mdi/close';
|
||||
|
||||
const MARKER_COLOR = 'rgb(63,84,243)';
|
||||
const CLUSTER_DISABLE_ZOOM = 16; // Clustering disabled at zoom 16 and above
|
||||
|
||||
|
||||
@@ -12,14 +12,18 @@
|
||||
|
||||
<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-icon size="x-large" color="primary" class="mb-2">
|
||||
<ProgressIcon />
|
||||
</v-icon>
|
||||
<v-list-item-title class="font-weight-bold">The map is incomplete!</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
New locations are always being added.
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item class="my-4">
|
||||
<v-icon size="x-large" color="primary" class="mb-2">mdi-square-edit-outline</v-icon>
|
||||
<v-icon size="x-large" color="primary" class="mb-2">
|
||||
<EditIcon />
|
||||
</v-icon>
|
||||
<v-list-item-title class="font-weight-bold">Add missing points!</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
Know of a missing ALPR? <router-link to="/report/id">Contribute</router-link> to the map.
|
||||
@@ -38,6 +42,9 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import ProgressIcon from '@iconify-vue/mdi/progress-pencil';
|
||||
import EditIcon from '@iconify-vue/mdi/square-edit-outline';
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<v-col cols="12" sm="6" class="text-center">
|
||||
<v-select
|
||||
color="rgb(18, 151, 195)"
|
||||
prepend-inner-icon="mdi-factory"
|
||||
:prepend-inner-icon="CompanyIcon"
|
||||
v-model="selectedBrand"
|
||||
:items="alprBrands"
|
||||
item-title="nickname"
|
||||
@@ -22,7 +22,7 @@
|
||||
max-width="100%"
|
||||
class="my-4"
|
||||
></v-img>
|
||||
<v-btn to="/what-is-an-alpr#photos" color="#1297C3" variant="tonal" size="small"><v-icon start>mdi-image-multiple</v-icon> See All Photos</v-btn>
|
||||
<v-btn to="/identify" color="#1297C3" variant="tonal" size="small"><v-icon start><ImageMultipleIcon /></v-icon> See All Photos</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6">
|
||||
@@ -42,7 +42,7 @@
|
||||
</DFCode>
|
||||
<div class="text-caption text-center mt-1">
|
||||
<a href="https://www.wikidata.org/wiki/Wikidata:Main_Page" target="_blank" rel="noopener" class="text-decoration-none text-grey-darken-1">
|
||||
What is WikiData? <v-icon size="x-small">mdi-open-in-new</v-icon>
|
||||
What is WikiData? <v-icon size="x-small"><OpenInNewIcon /></v-icon>
|
||||
</a>
|
||||
</div>
|
||||
</v-col>
|
||||
@@ -54,6 +54,10 @@ import DFCode from '@/components/DFCode.vue';
|
||||
import { ref, type Ref } from 'vue';
|
||||
import type { WikidataItem } from '@/types';
|
||||
|
||||
import CompanyIcon from '@iconify-vue/mdi/company';
|
||||
import ImageMultipleIcon from '@iconify-vue/mdi/image-multiple';
|
||||
import OpenInNewIcon from '@iconify-vue/mdi/open-in-new';
|
||||
|
||||
const alprBrands: WikidataItem[] = [
|
||||
{
|
||||
name: 'Flock Safety',
|
||||
|
||||
@@ -39,10 +39,11 @@
|
||||
<template v-slot:placeholder>
|
||||
<v-row class="fill-height ma-0" align="center" justify="center">
|
||||
<v-icon
|
||||
icon="mdi-web"
|
||||
size="x-large"
|
||||
color="grey-lighten-2"
|
||||
></v-icon>
|
||||
>
|
||||
<WebIcon />
|
||||
</v-icon>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-img>
|
||||
@@ -50,11 +51,12 @@
|
||||
<!-- Overlay with external link icon -->
|
||||
<div class="project-overlay">
|
||||
<v-icon
|
||||
icon="mdi-open-in-new"
|
||||
color="white"
|
||||
size="large"
|
||||
class="external-link-icon"
|
||||
></v-icon>
|
||||
>
|
||||
<OpenInNewIcon />
|
||||
</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -78,10 +80,14 @@
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="mdi-arrow-top-right"
|
||||
block
|
||||
@click.stop
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon>
|
||||
<ArrowTopRightIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
Visit Site
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
@@ -92,6 +98,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import WebIcon from '@iconify-vue/mdi/web';
|
||||
import OpenInNewIcon from '@iconify-vue/mdi/open-in-new';
|
||||
import ArrowTopRightIcon from '@iconify-vue/mdi/arrow-top-right';
|
||||
|
||||
interface SimilarProject {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
:aria-label="link.alt"
|
||||
>
|
||||
<v-list-item-title class="d-flex align-center">
|
||||
<v-icon class="custom-icon" start :icon="link.icon" :alt="link.alt" />
|
||||
<v-icon start :alt="link.alt">
|
||||
<component :is="link.icon" />
|
||||
</v-icon>
|
||||
{{ link.title }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
@@ -42,8 +44,9 @@
|
||||
role="listitem"
|
||||
>
|
||||
<v-list-item-title class="d-flex align-center justify-start">
|
||||
<v-icon start v-if="link.icon" class="custom-icon" :icon="link.icon"></v-icon>
|
||||
<img v-else-if="link.customIcon" class="mr-2 custom-icon" width="24" height="24" :src="isDark ? link.customIconDark : link.customIcon" :alt="link.alt" />
|
||||
<v-icon start :alt="link.alt">
|
||||
<component :is="link.icon" />
|
||||
</v-icon>
|
||||
{{ link.title }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
@@ -70,29 +73,36 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useTheme } from 'vuetify';
|
||||
|
||||
import InformationIcon from '@iconify-vue/mdi/information';
|
||||
import ShieldLockIcon from '@iconify-vue/mdi/shield-lock';
|
||||
import FileDocumentIcon from '@iconify-vue/mdi/file-document';
|
||||
import NewspaperIcon from '@iconify-vue/mdi/newspaper';
|
||||
import EmailIcon from '@iconify-vue/mdi/email';
|
||||
import HeartIcon from '@iconify-vue/mdi/heart';
|
||||
import GithubIcon from '@iconify-vue/mdi/github';
|
||||
import DiscordIcon from '@iconify-vue/ic/baseline-discord';
|
||||
|
||||
const theme = useTheme();
|
||||
const isDark = computed(() => theme.name.value === 'dark');
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const internalLinks = [
|
||||
{ title: 'About', to: '/about', icon: 'mdi-information', alt: 'About' },
|
||||
{ title: 'Privacy Policy', to: '/privacy', icon: 'mdi-shield-lock', alt: 'Privacy Policy' },
|
||||
{ title: 'Terms of Service', to: '/terms', icon: 'mdi-file-document', alt: 'Terms of Service' },
|
||||
{ title: 'Press', to: '/press', icon: 'mdi-newspaper', alt: 'Press' },
|
||||
{ title: 'Contact', to: '/contact', icon: 'mdi-email', alt: 'Contact' },
|
||||
{ title: 'About', to: '/about', icon: InformationIcon, alt: 'About' },
|
||||
{ title: 'Privacy Policy', to: '/privacy', icon: ShieldLockIcon, alt: 'Privacy Policy' },
|
||||
{ title: 'Terms of Service', to: '/terms', icon: FileDocumentIcon, alt: 'Terms of Service' },
|
||||
{ title: 'Press', to: '/press', icon: NewspaperIcon, alt: 'Press' },
|
||||
{ title: 'Contact', to: '/contact', icon: EmailIcon, alt: 'Contact' },
|
||||
];
|
||||
|
||||
const externalLinks = [
|
||||
{ title: 'Discord', href: 'https://discord.gg/aV7v4R3sKT', customIcon: '/icon-discord.svg', customIconDark: '/icon-discord-white.svg', alt: 'Discord Logo' },
|
||||
{ title: 'Donate', to: '/donate', icon: 'mdi-heart', alt: 'Donate' },
|
||||
{ title: 'GitHub', href: 'https://github.com/FoggedLens/deflock', icon: 'mdi-github', alt: 'GitHub Logo' },
|
||||
{ title: 'Discord', href: 'https://discord.gg/aV7v4R3sKT', icon: DiscordIcon, alt: 'Discord Logo' },
|
||||
{ title: 'Donate', to: '/donate', icon: HeartIcon, alt: 'Donate' },
|
||||
{ title: 'GitHub', href: 'https://github.com/FoggedLens/deflock', icon: GithubIcon, alt: 'GitHub Logo' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-icon {
|
||||
opacity: var(--v-medium-emphasis-opacity);
|
||||
}
|
||||
.copyright p {
|
||||
font-size: 0.85rem;
|
||||
line-height: 0.5rem;
|
||||
|
||||
@@ -10,7 +10,6 @@ import 'vuetify/styles'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
import '@mdi/font/css/materialdesignicons.css' // Ensure you are using css-loader
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
|
||||
@@ -8,13 +8,16 @@
|
||||
<p class="caption">... just like the license plate this guy is searching for.</p>
|
||||
</div>
|
||||
|
||||
<v-btn color="primary" to="/"><v-icon start>mdi-home</v-icon>Home</v-btn>
|
||||
<v-btn color="primary" to="/"><v-icon start>
|
||||
<HomeIcon />
|
||||
</v-icon>Home</v-btn>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import HomeIcon from '@iconify-vue/mdi/home';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -25,11 +25,12 @@
|
||||
>
|
||||
<v-card-item class="text-center pa-6">
|
||||
<v-icon
|
||||
icon="mdi-camera"
|
||||
size="48"
|
||||
color="primary"
|
||||
class="mb-4"
|
||||
></v-icon>
|
||||
>
|
||||
<CameraIcon />
|
||||
</v-icon>
|
||||
<v-card-title class="text-h5 font-weight-bold card-title-wrap">
|
||||
Report a Camera
|
||||
</v-card-title>
|
||||
@@ -41,7 +42,7 @@
|
||||
<v-btn
|
||||
color="primary"
|
||||
size="large"
|
||||
append-icon="mdi-arrow-right"
|
||||
:append-icon="ArrowRightIcon"
|
||||
>
|
||||
Start Report
|
||||
</v-btn>
|
||||
@@ -61,11 +62,12 @@
|
||||
>
|
||||
<v-card-item class="text-center pa-6">
|
||||
<v-icon
|
||||
:icon="showContactOptions ? 'mdi-message-text' : 'mdi-message-text'"
|
||||
size="48"
|
||||
color="secondary"
|
||||
class="mb-4"
|
||||
></v-icon>
|
||||
>
|
||||
<MessageTextIcon />
|
||||
</v-icon>
|
||||
<v-card-title class="text-h5 font-weight-bold card-title-wrap">
|
||||
{{ showContactOptions ? 'Get in Touch' : 'General Inquiry' }}
|
||||
</v-card-title>
|
||||
@@ -78,7 +80,7 @@
|
||||
<v-btn
|
||||
color="secondary"
|
||||
size="large"
|
||||
append-icon="mdi-arrow-right"
|
||||
:append-icon="ArrowRightIcon"
|
||||
>
|
||||
Contact Options
|
||||
</v-btn>
|
||||
@@ -133,10 +135,13 @@
|
||||
<script setup lang="ts">
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import { useTheme } from 'vuetify';
|
||||
import { computed, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import CameraIcon from '@iconify-vue/mdi/camera';
|
||||
import ArrowRightIcon from '@iconify-vue/mdi/arrow-right';
|
||||
import MessageTextIcon from '@iconify-vue/mdi/message-text';
|
||||
|
||||
const theme = useTheme();
|
||||
const isDark = computed(() => theme.name.value === 'dark');
|
||||
const showContactOptions = ref(false);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
<v-row>
|
||||
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
||||
<div class="text-center mb-8">
|
||||
<v-icon size="64" color="var(--df-blue)" class="mb-4">mdi-account-voice</v-icon>
|
||||
<v-icon size="64" color="var(--df-blue)" class="mb-4">
|
||||
<AccountVoiceIcon />
|
||||
</v-icon>
|
||||
<h2 class="text-h4 mb-4 font-weight-bold">Your Voice Matters Locally</h2>
|
||||
<p class="text-h6 text-medium-emphasis serif">
|
||||
City council members rely on us to understand public opinion. Here's your step-by-step guide to effectively advocate
|
||||
@@ -36,13 +38,17 @@
|
||||
>
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon color="primary" class="mr-3 align-self-center">mdi-comment-alert</v-icon>
|
||||
<v-icon color="primary" class="mr-3 align-self-center">
|
||||
<CommentAlertIcon />
|
||||
</v-icon>
|
||||
<div>
|
||||
<h4 class="text-h6 font-weight-bold mb-1">Talking Points</h4>
|
||||
<p class="text-body-2 mb-0">Common questions, arguments & responses for discussing surveillance</p>
|
||||
</div>
|
||||
</div>
|
||||
<v-icon color="primary" class="align-self-center">mdi-open-in-new</v-icon>
|
||||
<v-icon color="primary" class="align-self-center">
|
||||
<OpenInNewIcon />
|
||||
</v-icon>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
@@ -70,40 +76,44 @@
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h4 class="text-h6 mb-3 d-flex align-center">
|
||||
<v-icon color="primary" class="mr-2">mdi-calendar-plus</v-icon>
|
||||
<v-icon color="primary" class="mr-2">
|
||||
<CalendarPlusIcon />
|
||||
</v-icon>
|
||||
How to Schedule
|
||||
</h4>
|
||||
<v-list density="compact">
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Contact their office via phone or email</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Suggest meeting at a local coffee shop or their office</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Request just 15-20 minutes of their time</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Mention you're a constituent concerned about ALPRs</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h4 class="text-h6 mb-3 d-flex align-center">
|
||||
<v-icon color="primary" class="mr-2">mdi-lightbulb-on</v-icon>
|
||||
<v-icon color="primary" class="mr-2">
|
||||
<LightbulbOnIcon />
|
||||
</v-icon>
|
||||
Meeting Tips
|
||||
</h4>
|
||||
<v-list density="compact">
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Bring a brief printed summary of key points</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Share personal concerns about privacy and community impact</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Ask about their position and listen to their concerns</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Respond to their concerns with your ideas</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -148,40 +158,44 @@
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h4 class="text-h6 mb-3 d-flex align-center">
|
||||
<v-icon color="primary" class="mr-2">mdi-clock</v-icon>
|
||||
<v-icon color="primary" class="mr-2">
|
||||
<ClockIcon />
|
||||
</v-icon>
|
||||
Before the Meeting
|
||||
</h4>
|
||||
<v-list density="compact">
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Check meeting schedule and agenda online</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Sign up for public comment (often required)</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Prepare 2-3 minute statement (practice timing)</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Bring a copy of your statement</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<h4 class="text-h6 mb-3 d-flex align-center">
|
||||
<v-icon color="primary" class="mr-2">mdi-presentation</v-icon>
|
||||
<v-icon color="primary" class="mr-2">
|
||||
<PresentationIcon />
|
||||
</v-icon>
|
||||
During the Meeting
|
||||
</h4>
|
||||
<v-list density="compact">
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Arrive on time or early</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>State your name and connection to the city</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Speak clearly and maintain eye contact</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-check">
|
||||
<v-list-item :prepend-icon="CheckIcon">
|
||||
<v-list-item-title>Stay respectful and thank council for their time</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -197,7 +211,9 @@
|
||||
<!-- Example Videos -->
|
||||
<div class="mt-6">
|
||||
<h4 class="text-h6 font-weight-bold mb-4 d-flex align-center">
|
||||
<v-icon color="primary" class="mr-2">mdi-video</v-icon>
|
||||
<v-icon color="primary" class="mr-2">
|
||||
<VideoIcon />
|
||||
</v-icon>
|
||||
Example Speeches
|
||||
</h4>
|
||||
<v-row>
|
||||
@@ -210,7 +226,9 @@
|
||||
@click="openVideo(video.url)"
|
||||
>
|
||||
<v-avatar size="48" color="primary" class="mr-3 video-play-button-compact">
|
||||
<v-icon size="24" color="white">mdi-play</v-icon>
|
||||
<v-icon size="24" color="white">
|
||||
<PlayIcon />
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
|
||||
<div class="flex-grow-1">
|
||||
@@ -232,7 +250,9 @@
|
||||
<v-card class="pa-6" elevation="3" rounded="lg">
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-avatar size="48" color="primary" class="mr-4">
|
||||
<v-icon size="24" color="white">mdi-trophy</v-icon>
|
||||
<v-icon size="24" color="white">
|
||||
<TrophyOutlineIcon />
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
<div>
|
||||
<h3 class="text-h5 font-weight-bold mb-1">Recent Victories</h3>
|
||||
@@ -254,28 +274,34 @@
|
||||
>
|
||||
<template v-slot:header.cityState="{ column }">
|
||||
<div class="d-flex align-center text-medium-emphasis">
|
||||
<v-icon icon="mdi-map-marker" size="18" class="mr-2" />
|
||||
<v-icon size="18" class="mr-2">
|
||||
<MapMarkerIcon />
|
||||
</v-icon>
|
||||
<span class="text-caption font-weight-medium">{{ column.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:header.MonthYear="{ column }">
|
||||
<div class="d-flex align-center text-medium-emphasis">
|
||||
<v-icon icon="mdi-calendar-month" size="18" class="mr-2" />
|
||||
<v-icon size="18" class="mr-2">
|
||||
<CalendarMonthIcon />
|
||||
</v-icon>
|
||||
<span class="text-caption font-weight-medium">{{ column.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:header.Outcome="{ column }">
|
||||
<div class="d-flex align-center text-medium-emphasis">
|
||||
<v-icon icon="mdi-trophy-outline" size="18" class="mr-2" />
|
||||
<v-icon size="18" class="mr-2">
|
||||
<TrophyOutlineIcon />
|
||||
</v-icon>
|
||||
<span class="text-caption font-weight-medium">{{ column.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.data-table-expand="{ internalItem, isExpanded, toggleExpand }">
|
||||
<v-btn
|
||||
:append-icon="isExpanded(internalItem) ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
||||
:append-icon="isExpanded(internalItem) ? ChevronUpIcon : ChevronDownIcon"
|
||||
:text="isExpanded(internalItem) ? 'Collapse' : 'More info'"
|
||||
class="text-none"
|
||||
color="medium-emphasis"
|
||||
@@ -304,7 +330,9 @@
|
||||
</template>
|
||||
|
||||
<template v-slot:item.Outcome="{ item }">
|
||||
<v-icon icon="mdi-check-bold" size="18" class="mr-2" />
|
||||
<v-icon size="18" class="mr-2">
|
||||
<CheckBoldIcon />
|
||||
</v-icon>
|
||||
<span class="font-weight-bold">{{ item.Outcome }}</span>
|
||||
</template>
|
||||
|
||||
@@ -314,7 +342,9 @@
|
||||
|
||||
<template v-slot:no-data>
|
||||
<div class="text-center py-8">
|
||||
<v-icon size="48" color="grey-lighten-1" class="mb-4">mdi-database-off</v-icon>
|
||||
<v-icon size="48" color="grey-lighten-1" class="mb-4">
|
||||
<DatabaseOffIcon />
|
||||
</v-icon>
|
||||
<div class="text-h6 text-medium-emphasis">No victories found</div>
|
||||
<div class="text-body-2 text-medium-emphasis mt-2">
|
||||
Check your connection and try again
|
||||
@@ -324,7 +354,7 @@
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
class="mt-4"
|
||||
prepend-icon="mdi-refresh"
|
||||
:prepend-icon="RefreshIcon"
|
||||
>
|
||||
Retry
|
||||
</v-btn>
|
||||
@@ -345,7 +375,9 @@
|
||||
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
||||
<v-card class="pa-6" elevation="3" rounded="lg" color="primary" variant="tonal">
|
||||
<div class="text-center">
|
||||
<v-icon size="64" color="primary" class="mb-4">mdi-comment-question</v-icon>
|
||||
<v-icon size="64" color="primary" class="mb-4">
|
||||
<CommentQuestionIcon />
|
||||
</v-icon>
|
||||
<h3 class="text-h4 font-weight-bold mb-4">Need Help or Have Questions?</h3>
|
||||
<p class="text-h6 mb-6 serif">
|
||||
Join our supportive community of activists and experienced speakers who are ready to help you succeed.
|
||||
@@ -357,8 +389,12 @@
|
||||
size="large"
|
||||
color="primary"
|
||||
class="mr-4 mb-4"
|
||||
prepend-icon="mdi-discord"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<DiscordIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
Join #campaigning Channel
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -374,6 +410,27 @@ import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import Hero from '@/components/layout/Hero.vue';
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
|
||||
import AccountVoiceIcon from '@iconify-vue/mdi/account-voice';
|
||||
import CommentAlertIcon from '@iconify-vue/mdi/comment-alert';
|
||||
import OpenInNewIcon from '@iconify-vue/mdi/open-in-new';
|
||||
import CalendarPlusIcon from '@iconify-vue/mdi/calendar-plus';
|
||||
import CheckIcon from '@iconify-vue/mdi/check';
|
||||
import CheckBoldIcon from '@iconify-vue/mdi/check-bold';
|
||||
import PresentationIcon from '@iconify-vue/mdi/presentation';
|
||||
import VideoIcon from '@iconify-vue/mdi/video';
|
||||
import CommentQuestionIcon from '@iconify-vue/mdi/comment-question';
|
||||
import MapMarkerIcon from '@iconify-vue/mdi/map-marker';
|
||||
import CalendarMonthIcon from '@iconify-vue/mdi/calendar-month';
|
||||
import TrophyOutlineIcon from '@iconify-vue/mdi/trophy-outline';
|
||||
import ChevronUpIcon from '@iconify-vue/mdi/chevron-up';
|
||||
import ChevronDownIcon from '@iconify-vue/mdi/chevron-down';
|
||||
import DatabaseOffIcon from '@iconify-vue/mdi/database-off';
|
||||
import RefreshIcon from '@iconify-vue/mdi/refresh';
|
||||
import LightbulbOnIcon from '@iconify-vue/mdi/lightbulb-on';
|
||||
import ClockIcon from '@iconify-vue/mdi/clock';
|
||||
import DiscordIcon from '@iconify-vue/ic/baseline-discord';
|
||||
import PlayIcon from '@iconify-vue/mdi/play';
|
||||
|
||||
const sortMonthYearByDateDesc = (a: string, b: string) => {
|
||||
const [aMonth, aYear] = a.split(/\s/);
|
||||
const [bMonth, bYear] = b.split(/\s/);
|
||||
|
||||
@@ -20,10 +20,14 @@
|
||||
size="large"
|
||||
color="black"
|
||||
class="download-btn ios-btn"
|
||||
prepend-icon="mdi-apple"
|
||||
:href="appLinks.ios"
|
||||
target="_blank"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<AppleIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
Download for iOS
|
||||
</v-btn>
|
||||
<v-btn
|
||||
@@ -31,10 +35,14 @@
|
||||
variant="outlined"
|
||||
color="black"
|
||||
class="download-btn android-btn"
|
||||
prepend-icon="mdi-google-play"
|
||||
:href="appLinks.android"
|
||||
target="_blank"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<GooglePlayIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
Get on Android
|
||||
</v-btn>
|
||||
|
||||
@@ -43,11 +51,17 @@
|
||||
variant="tonal"
|
||||
color="white"
|
||||
to="/app/docs"
|
||||
prepend-icon="mdi-book-open-variant"
|
||||
class="download-btn mt-8"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<BookOpenIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
Read the User Guide
|
||||
<v-icon icon="mdi-open-in-new" size="small" class="ml-1" />
|
||||
<v-icon size="small" class="ml-1">
|
||||
<OpenInNewIcon />
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,7 +94,9 @@
|
||||
<v-card class="feature-card" elevation="4">
|
||||
<v-card-text class="text-center pa-8">
|
||||
<div class="feature-icon mb-6">
|
||||
<v-icon :icon="feature.icon" size="48" :color="feature.color" />
|
||||
<v-icon size="48" :color="feature.color">
|
||||
<component :is="feature.icon" />
|
||||
</v-icon>
|
||||
</div>
|
||||
<h3 class="feature-title mb-3">{{ feature.title }}</h3>
|
||||
<p class="feature-description">{{ feature.description }}</p>
|
||||
@@ -119,17 +135,6 @@
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<!-- <div class="screenshots-toggle" style="text-align:center; margin-top:32px;">
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
@click="showAllScreenshots = !showAllScreenshots"
|
||||
rounded
|
||||
size="large"
|
||||
>
|
||||
{{ showAllScreenshots ? 'Show Less' : 'Show More Screenshots' }}
|
||||
</v-btn>
|
||||
</div> -->
|
||||
</div>
|
||||
</v-container>
|
||||
</section>
|
||||
@@ -169,7 +174,9 @@
|
||||
>
|
||||
<v-card-text class="pa-8">
|
||||
<h3 class="principle-title mb-3">
|
||||
<v-icon icon="mdi-lock" color="primary" class="me-3" />
|
||||
<v-icon color="primary" class="me-3">
|
||||
<LockIcon />
|
||||
</v-icon>
|
||||
{{ principle.title }}
|
||||
</h3>
|
||||
<p class="principle-description mb-0">
|
||||
@@ -196,10 +203,14 @@
|
||||
size="x-large"
|
||||
color="primary"
|
||||
class="cta-btn ios-cta"
|
||||
prepend-icon="mdi-apple"
|
||||
:href="appLinks.ios"
|
||||
target="_blank"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<AppleIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
Download for iPhone
|
||||
</v-btn>
|
||||
<v-btn
|
||||
@@ -207,10 +218,14 @@
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
class="cta-btn android-cta"
|
||||
prepend-icon="mdi-google-play"
|
||||
:href="appLinks.android"
|
||||
target="_blank"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<GooglePlayIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
Get on Android
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -226,11 +241,20 @@
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
import AppleIcon from '@iconify-vue/mdi/apple';
|
||||
import GooglePlayIcon from '@iconify-vue/mdi/google-play';
|
||||
import BookOpenIcon from '@iconify-vue/mdi/book-open-variant';
|
||||
import OpenInNewIcon from '@iconify-vue/mdi/open-in-new';
|
||||
import MapSearchIcon from '@iconify-vue/mdi/map-search';
|
||||
import CameraPlusIcon from '@iconify-vue/mdi/camera-plus';
|
||||
import AccountGroupIcon from '@iconify-vue/mdi/account-group';
|
||||
import LockIcon from '@iconify-vue/mdi/lock';
|
||||
|
||||
interface Feature {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
icon: any;
|
||||
color: string;
|
||||
}
|
||||
|
||||
@@ -247,12 +271,6 @@ interface Statistic {
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface PrivacyFeature {
|
||||
id: number;
|
||||
text: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
interface PrivacyPrinciple {
|
||||
id: number;
|
||||
title: string;
|
||||
@@ -270,21 +288,21 @@ const features: Feature[] = [
|
||||
id: 1,
|
||||
title: 'Discover ALPRs',
|
||||
description: 'Find automatic license plate readers in your neighborhood with our comprehensive database.',
|
||||
icon: 'mdi-map-search',
|
||||
icon: MapSearchIcon,
|
||||
color: 'primary'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Report New Cameras',
|
||||
description: 'Easily report new ALPR installations you discover to help grow the community database.',
|
||||
icon: 'mdi-camera-plus',
|
||||
icon: CameraPlusIcon,
|
||||
color: 'success'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Community Driven',
|
||||
description: 'Join a community of privacy advocates working together to map surveillance.',
|
||||
icon: 'mdi-account-group',
|
||||
icon: AccountGroupIcon,
|
||||
color: 'info'
|
||||
},
|
||||
];
|
||||
@@ -330,31 +348,6 @@ const statistics: Statistic[] = [
|
||||
},
|
||||
];
|
||||
|
||||
// Privacy features
|
||||
const privacyFeatures: PrivacyFeature[] = [
|
||||
{
|
||||
id: 1,
|
||||
text: 'No personal data collection',
|
||||
icon: 'mdi-check-circle'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: 'No advertising or tracking',
|
||||
icon: 'mdi-check-circle'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: 'Open source transparency',
|
||||
icon: 'mdi-check-circle'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: 'Local data storage',
|
||||
icon: 'mdi-check-circle'
|
||||
}
|
||||
];
|
||||
|
||||
// Privacy principles for detailed policy
|
||||
const privacyPrinciples: PrivacyPrinciple[] = [
|
||||
{
|
||||
id: 1,
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
>
|
||||
<v-card class="toc-sidebar" elevation="1" sticky>
|
||||
<v-card-title class="text-h6 py-3">
|
||||
<v-icon start>mdi-format-list-bulleted</v-icon>
|
||||
<v-icon start>
|
||||
<ListBulletedIcon />
|
||||
</v-icon>
|
||||
Table of Contents
|
||||
</v-card-title>
|
||||
<v-divider />
|
||||
@@ -109,6 +111,8 @@ import { ref, onMounted, nextTick, watch } from 'vue';
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import Hero from '@/components/layout/Hero.vue';
|
||||
|
||||
import ListBulletedIcon from '@iconify-vue/mdi/format-list-bulleted';
|
||||
|
||||
interface CMSResponse {
|
||||
data: {
|
||||
id: number;
|
||||
|
||||
@@ -143,10 +143,14 @@
|
||||
size="x-large"
|
||||
color="primary"
|
||||
to="/report"
|
||||
prepend-icon="mdi-map-marker-plus"
|
||||
variant="elevated"
|
||||
class="mr-4"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<MapMarkerPlusIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
Add to Map
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
@@ -160,6 +164,8 @@
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import Hero from '@/components/layout/Hero.vue';
|
||||
|
||||
import MapMarkerPlusIcon from '@iconify-vue/mdi/map-marker-plus';
|
||||
|
||||
function openImageInNewTab(url: string) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<v-btn size="large" color="rgb(18, 151, 195)" large @click="goToMap({ withCurrentLocation: true })">
|
||||
Explore the Map
|
||||
<v-icon end>mdi-map</v-icon>
|
||||
<v-icon end><MapIcon /></v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -59,7 +59,7 @@
|
||||
<v-col cols="12" md="4" class="text-center">
|
||||
<v-card>
|
||||
<v-card-title class="headline">
|
||||
<v-icon x-large class="mr-2">mdi-shield-alert</v-icon>
|
||||
<v-icon x-large class="mr-2"><ShieldAlertIcon /></v-icon>
|
||||
Privacy Violations
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
@@ -70,7 +70,7 @@
|
||||
<v-col cols="12" md="4" class="text-center">
|
||||
<v-card>
|
||||
<v-card-title class="headline">
|
||||
<v-icon x-large class="mr-2">mdi-robber</v-icon>
|
||||
<v-icon x-large class="mr-2"><RobberIcon /></v-icon>
|
||||
Risk of Misuse
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
@@ -81,7 +81,7 @@
|
||||
<v-col cols="12" md="4" class="text-center">
|
||||
<v-card>
|
||||
<v-card-title class="headline">
|
||||
<v-icon x-large class="mr-2">mdi-handcuffs</v-icon>
|
||||
<v-icon x-large class="mr-2"><HandcuffsIcon /></v-icon>
|
||||
Limited Benefits
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
@@ -96,7 +96,7 @@
|
||||
</p>
|
||||
|
||||
<v-btn class="my-4" color="rgb(18, 151, 195)" large to="/what-is-an-alpr">
|
||||
<v-icon start>mdi-book-open-page-variant</v-icon>
|
||||
<v-icon start><BookOpenPageVariantIcon /></v-icon>
|
||||
Learn about ALPRs
|
||||
</v-btn>
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<v-btn class="mt-4" variant="outlined" color="rgb(18, 151, 195)" to="/what-is-an-alpr#similar">
|
||||
Learn more about Flock
|
||||
<v-icon end>mdi-arrow-right</v-icon>
|
||||
<v-icon end><ArrowRightIcon /></v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-container>
|
||||
@@ -123,12 +123,68 @@
|
||||
<h2 class="display-2 mb-4">Explore ALPR Locations Near You</h2>
|
||||
<v-btn color="white" large @click="goToMap({ withCurrentLocation: true })">
|
||||
View the Map
|
||||
<v-icon end>mdi-map</v-icon>
|
||||
<v-icon end><MapIcon /></v-icon>
|
||||
</v-btn>
|
||||
</v-container>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import ALPRCounter from '@/components/ALPRCounter.vue';
|
||||
import { useGlobalStore } from '@/stores/global';
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
|
||||
import MapIcon from '@iconify-vue/mdi/map';
|
||||
import ShieldAlertIcon from '@iconify-vue/mdi/shield-alert';
|
||||
import RobberIcon from '@iconify-vue/mdi/robber';
|
||||
import HandcuffsIcon from '@iconify-vue/mdi/handcuffs';
|
||||
import BookOpenPageVariantIcon from '@iconify-vue/mdi/book-open-page-variant';
|
||||
import ArrowRightIcon from '@iconify-vue/mdi/arrow-right';
|
||||
|
||||
const router = useRouter();
|
||||
const { setCurrentLocation } = useGlobalStore();
|
||||
|
||||
interface GoToMapOptions {
|
||||
withCurrentLocation?: boolean;
|
||||
}
|
||||
|
||||
const featuredOn = [
|
||||
{
|
||||
name: 'Forbes',
|
||||
logo: '/white-logos/forbes.svg',
|
||||
url: 'https://www.forbes.com/sites/larsdaniel/2024/11/26/think-youre-not-being-watched-deflock-says-think-again/',
|
||||
},
|
||||
{
|
||||
name: '404 Media',
|
||||
logo: '/white-logos/404media.svg',
|
||||
url: 'https://www.404media.co/the-open-source-project-deflock-is-mapping-license-plate-surveillance-cameras-all-over-the-world/',
|
||||
},
|
||||
{
|
||||
name: 'LA Times',
|
||||
logo: '/white-logos/latimes.svg',
|
||||
url: 'https://www.latimes.com/california/story/2024-11-14/are-there-automated-license-plate-readers-in-your-city-theres-an-open-source-program-for-that',
|
||||
wide: true,
|
||||
}
|
||||
];
|
||||
|
||||
async function goToMap(options: GoToMapOptions = {}) {
|
||||
if (options.withCurrentLocation) {
|
||||
setCurrentLocation()
|
||||
.then((currentLocation) => {
|
||||
const [lat, lon] = currentLocation;
|
||||
router.push({ path: '/map', hash: `#map=12/${lat.toFixed(6)}/${lon.toFixed(6)}` });
|
||||
})
|
||||
.catch(() => {
|
||||
router.push({ path: '/map' });
|
||||
});
|
||||
} else {
|
||||
router.push({ path: '/map' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
.hero-section {
|
||||
background: url('/hero.webp') no-repeat center center;
|
||||
@@ -214,53 +270,4 @@
|
||||
width: 100%;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import ALPRCounter from '@/components/ALPRCounter.vue';
|
||||
import { useGlobalStore } from '@/stores/global';
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const { setCurrentLocation } = useGlobalStore();
|
||||
|
||||
interface GoToMapOptions {
|
||||
withCurrentLocation?: boolean;
|
||||
}
|
||||
|
||||
const featuredOn = [
|
||||
{
|
||||
name: 'Forbes',
|
||||
logo: '/white-logos/forbes.svg',
|
||||
url: 'https://www.forbes.com/sites/larsdaniel/2024/11/26/think-youre-not-being-watched-deflock-says-think-again/',
|
||||
},
|
||||
{
|
||||
name: '404 Media',
|
||||
logo: '/white-logos/404media.svg',
|
||||
url: 'https://www.404media.co/the-open-source-project-deflock-is-mapping-license-plate-surveillance-cameras-all-over-the-world/',
|
||||
},
|
||||
{
|
||||
name: 'LA Times',
|
||||
logo: '/white-logos/latimes.svg',
|
||||
url: 'https://www.latimes.com/california/story/2024-11-14/are-there-automated-license-plate-readers-in-your-city-theres-an-open-source-program-for-that',
|
||||
wide: true,
|
||||
}
|
||||
];
|
||||
|
||||
async function goToMap(options: GoToMapOptions = {}) {
|
||||
if (options.withCurrentLocation) {
|
||||
setCurrentLocation()
|
||||
.then((currentLocation) => {
|
||||
const [lat, lon] = currentLocation;
|
||||
router.push({ path: '/map', hash: `#map=12/${lat.toFixed(6)}/${lon.toFixed(6)}` });
|
||||
})
|
||||
.catch(() => {
|
||||
router.push({ path: '/map' });
|
||||
});
|
||||
} else {
|
||||
router.push({ path: '/map' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</style>
|
||||
@@ -18,7 +18,6 @@
|
||||
:density="xs ? 'compact' : 'default'"
|
||||
class="map-search"
|
||||
ref="searchField"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
placeholder="Search for a location"
|
||||
single-line
|
||||
variant="solo"
|
||||
@@ -27,9 +26,12 @@
|
||||
v-model="searchInput"
|
||||
type="search"
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<v-icon><MagnifyIcon /></v-icon>
|
||||
</template>
|
||||
<template v-slot:append-inner>
|
||||
<v-btn :disabled="!searchInput" variant="text" flat color="#0080BC" @click="onSearch">
|
||||
Go<v-icon end>mdi-chevron-right</v-icon>
|
||||
Go<v-icon end><ChevronRightIcon /></v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
@@ -38,7 +40,9 @@
|
||||
|
||||
<!-- CURRENT LOCATION -->
|
||||
<template v-slot:bottomright>
|
||||
<v-fab icon="mdi-crosshairs-gps" @click="goToUserLocation" />
|
||||
<v-fab icon @click="goToUserLocation">
|
||||
<v-icon><CrosshairsGpsIcon /></v-icon>
|
||||
</v-fab>
|
||||
</template>
|
||||
</leaflet-map>
|
||||
<div v-else class="loader">
|
||||
@@ -64,6 +68,11 @@ import 'leaflet/dist/leaflet.css'
|
||||
import LeafletMap from '@/components/LeafletMap.vue';
|
||||
import NewVisitor from '@/components/NewVisitor.vue';
|
||||
|
||||
// Icons
|
||||
import MagnifyIcon from '@iconify-vue/mdi/magnify';
|
||||
import ChevronRightIcon from '@iconify-vue/mdi/chevron-right';
|
||||
import CrosshairsGpsIcon from '@iconify-vue/mdi/crosshairs-gps';
|
||||
|
||||
const DEFAULT_ZOOM = 12;
|
||||
|
||||
const zoom: Ref<number> = ref(DEFAULT_ZOOM);
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
color="blue"
|
||||
variant="tonal"
|
||||
to="/identify"
|
||||
prepend-icon="mdi-image-search"
|
||||
size="large"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon><ImageSearchIcon /></v-icon>
|
||||
</template>
|
||||
View ALPR Gallery
|
||||
</v-btn>
|
||||
</v-col>
|
||||
@@ -60,7 +62,7 @@
|
||||
size="large"
|
||||
>
|
||||
Download App
|
||||
<v-icon icon="mdi-arrow-right" end></v-icon>
|
||||
<v-icon end><ArrowRightIcon /></v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@@ -98,7 +100,7 @@
|
||||
size="large"
|
||||
>
|
||||
How to Edit
|
||||
<v-icon icon="mdi-arrow-right" end></v-icon>
|
||||
<v-icon end><ArrowRightIcon /></v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@@ -111,6 +113,9 @@
|
||||
<script setup lang="ts">
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import ALPRVerificationDialog from '@/components/ALPRVerificationDialog.vue';
|
||||
|
||||
import ImageSearchIcon from '@iconify-vue/mdi/image-search';
|
||||
import ArrowRightIcon from '@iconify-vue/mdi/arrow-right';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -13,7 +13,13 @@
|
||||
Editing the Map
|
||||
</h1>
|
||||
|
||||
<v-stepper-vertical color="rgb(18, 151, 195)" v-model="step" flat non-linear class="my-8" edit-icon="mdi-home">
|
||||
<v-stepper-vertical
|
||||
color="rgb(18, 151, 195)"
|
||||
v-model="step"
|
||||
flat
|
||||
non-linear
|
||||
class="my-8"
|
||||
>
|
||||
<template v-slot:default="{ step }: { step: any }">
|
||||
<v-stepper-vertical-item
|
||||
class="transparent"
|
||||
@@ -22,6 +28,11 @@
|
||||
value="1"
|
||||
editable
|
||||
>
|
||||
<template v-slot:icon="{ hasCompleted, step: targetStep }">
|
||||
<v-icon>
|
||||
<component :is="getStepperIcon(hasCompleted, targetStep)" />
|
||||
</v-icon>
|
||||
</template>
|
||||
<p>
|
||||
<a href="https://www.openstreetmap.org/user/new" target="_blank">Sign up for an OpenStreetMap account</a> in order to submit changes.
|
||||
</p>
|
||||
@@ -34,6 +45,11 @@
|
||||
value="2"
|
||||
editable
|
||||
>
|
||||
<template v-slot:icon="{ hasCompleted, step: targetStep }">
|
||||
<v-icon>
|
||||
<component :is="getStepperIcon(hasCompleted, targetStep)" />
|
||||
</v-icon>
|
||||
</template>
|
||||
<p>
|
||||
<a href="https://www.openstreetmap.org" target="_blank">Launch OpenStreetMap</a> and search for the location of the ALPR. You can use the search bar at the top of the page to find the location.
|
||||
</p>
|
||||
@@ -46,6 +62,11 @@
|
||||
value="3"
|
||||
editable
|
||||
>
|
||||
<template v-slot:icon="{ hasCompleted, step: targetStep }">
|
||||
<v-icon>
|
||||
<component :is="getStepperIcon(hasCompleted, targetStep)" />
|
||||
</v-icon>
|
||||
</template>
|
||||
<p>
|
||||
Once you've found the location of the ALPR, click the <strong>Edit</strong> button in the top left corner of the page. This will open the OpenStreetMap editor, where you can add the ALPR to the map.
|
||||
</p>
|
||||
@@ -53,9 +74,14 @@
|
||||
|
||||
<v-alert
|
||||
variant="tonal"
|
||||
type="warning"
|
||||
color="warning"
|
||||
class="mt-16 mb-6"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<AlertCircleIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
<p>
|
||||
Add cameras as <strong>standalone points only</strong>! Do not connect them to roads, buildings, or other objects. Place the point exactly where the camera is physically located, but keep it as an independent point on the map.
|
||||
</p>
|
||||
@@ -84,6 +110,11 @@
|
||||
value="4"
|
||||
editable
|
||||
>
|
||||
<template v-slot:icon="{ hasCompleted, step: targetStep }">
|
||||
<v-icon>
|
||||
<component :is="getStepperIcon(hasCompleted, targetStep)" />
|
||||
</v-icon>
|
||||
</template>
|
||||
<v-img
|
||||
max-width="450"
|
||||
class="my-8"
|
||||
@@ -110,15 +141,25 @@
|
||||
value="5"
|
||||
editable
|
||||
>
|
||||
<template v-slot:icon="{ hasCompleted, step: targetStep }">
|
||||
<v-icon>
|
||||
<component :is="getStepperIcon(hasCompleted, targetStep)" />
|
||||
</v-icon>
|
||||
</template>
|
||||
<p>
|
||||
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"
|
||||
type="info"
|
||||
color="info"
|
||||
class="my-6"
|
||||
title="How Long Will It Take?"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon>
|
||||
<AlertCircleIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
<p>
|
||||
We pull data from OpenStreetMap <i>hourly</i>, so it may take up to an hour for your changes to appear on this site. Rest assured that your changes will be reflected here soon. As we continue to scale, we hope to reduce this delay.
|
||||
</p>
|
||||
@@ -133,12 +174,27 @@
|
||||
<script setup lang="ts">
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import Hero from '@/components/layout/Hero.vue';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { ref, onMounted, watch, h } from 'vue';
|
||||
import OSMTagSelector from '@/components/OSMTagSelector.vue';
|
||||
import { VStepperVerticalItem, VStepperVertical } from 'vuetify/labs/components';
|
||||
|
||||
import PendingIcon from '@iconify-vue/mdi/timer-sand';
|
||||
import CompleteIcon from '@iconify-vue/mdi/check';
|
||||
import AlertCircleIcon from '@iconify-vue/mdi/alert-circle';
|
||||
|
||||
const step = ref(parseInt(localStorage.getItem('currentStep') || '1'));
|
||||
|
||||
// Reusable stepper icon template function
|
||||
const getStepperIcon = (hasCompleted: boolean, targetStep: number) => {
|
||||
if (step.value === targetStep) {
|
||||
return h(PendingIcon);
|
||||
} else if (hasCompleted) {
|
||||
return h(CompleteIcon);
|
||||
} else {
|
||||
return h('span', { style: 'font-style: normal;' }, targetStep.toString());
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
step.value = parseInt(localStorage.getItem('currentStep') || '1');
|
||||
});
|
||||
|
||||
@@ -42,22 +42,28 @@
|
||||
v-model="selectedType"
|
||||
:items="typeOptions"
|
||||
label="Filter by type"
|
||||
prepend-inner-icon="mdi-filter"
|
||||
variant="outlined"
|
||||
clearable
|
||||
hide-details
|
||||
class="filter-select"
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<v-icon><FilterIcon /></v-icon>
|
||||
</template>
|
||||
<template v-slot:item="{ props, item }">
|
||||
<v-list-item v-bind="props">
|
||||
<template v-slot:prepend>
|
||||
<v-icon :color="getTypeColor(item.raw.value)">{{ getTypeIcon(item.raw.value) }}</v-icon>
|
||||
<v-icon :color="getTypeColor(item.raw.value)">
|
||||
<component :is="getTypeIcon(item.raw.value)" />
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-slot:selection="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon :color="getTypeColor(item.raw.value)" class="mr-2">{{ getTypeIcon(item.raw.value) }}</v-icon>
|
||||
<v-icon :color="getTypeColor(item.raw.value)" class="mr-2">
|
||||
<component :is="getTypeIcon(item.raw.value)" />
|
||||
</v-icon>
|
||||
<span class="text-capitalize">{{ item.raw.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -110,12 +116,16 @@
|
||||
size="small"
|
||||
class="text-capitalize mb-2 font-weight-bold"
|
||||
>
|
||||
<v-icon start size="small">{{ getTypeIcon(printable.type) }}</v-icon>
|
||||
<template v-slot:prepend>
|
||||
<v-icon size="small" class="mr-2">
|
||||
<component :is="getTypeIcon(printable.type)" />
|
||||
</v-icon>
|
||||
</template>
|
||||
{{ deCamel(printable.type) }}
|
||||
</v-chip>
|
||||
|
||||
<div class="d-flex align-center text-caption text-grey mb-3">
|
||||
<v-icon size="small" class="mr-1">mdi-account</v-icon>
|
||||
<v-icon size="small" class="mr-1"><AccountIcon /></v-icon>
|
||||
by {{ printable.author }}
|
||||
<v-tooltip text="Licensed under CC BY-NC 4.0">
|
||||
<template v-slot:activator="{ props }">
|
||||
@@ -125,12 +135,12 @@
|
||||
class="ml-1"
|
||||
color="grey"
|
||||
>
|
||||
mdi-creative-commons
|
||||
<CreativeCommonsIcon />
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-spacer />
|
||||
<v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
|
||||
<v-icon size="small" class="mr-1"><ClockIcon /></v-icon>
|
||||
{{ formatDate(printable.date_updated) }}
|
||||
</div>
|
||||
|
||||
@@ -145,9 +155,11 @@
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
size="small"
|
||||
prepend-icon="mdi-download"
|
||||
class="flex-1-1-50"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon><DownloadIcon /></v-icon>
|
||||
</template>
|
||||
<span v-if="printable.back">Front Side</span>
|
||||
<span v-else>Download</span>
|
||||
</v-btn>
|
||||
@@ -160,9 +172,11 @@
|
||||
variant="tonal"
|
||||
color="secondary"
|
||||
size="small"
|
||||
prepend-icon="mdi-download"
|
||||
class="flex-1-1-50"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon><DownloadIcon /></v-icon>
|
||||
</template>
|
||||
Back Side
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -175,7 +189,7 @@
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="text-center py-12">
|
||||
<v-icon size="64" color="grey-lighten-1">mdi-inbox-outline</v-icon>
|
||||
<v-icon size="64" color="grey-lighten-1"><InboxIcon /></v-icon>
|
||||
<h3 class="text-h5 mt-4 mb-2 text-grey">No printables available</h3>
|
||||
<p class="text-grey">Check back later for new content!</p>
|
||||
</div>
|
||||
@@ -190,9 +204,11 @@
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
size="large"
|
||||
prepend-icon="mdi-upload"
|
||||
class="text-none"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon><UploadIcon /></v-icon>
|
||||
</template>
|
||||
Submit Your Artwork
|
||||
</v-btn>
|
||||
<p class="text-caption text-grey mt-2">
|
||||
@@ -209,6 +225,21 @@ import type { Ref } from 'vue';
|
||||
import DefaultLayout from '@/layouts/DefaultLayout.vue';
|
||||
import Hero from '@/components/layout/Hero.vue';
|
||||
|
||||
// Icon imports
|
||||
import FilterIcon from '@iconify-vue/mdi/filter';
|
||||
import AccountIcon from '@iconify-vue/mdi/account';
|
||||
import CreativeCommonsIcon from '@iconify-vue/mdi/creative-commons';
|
||||
import ClockIcon from '@iconify-vue/mdi/clock-outline';
|
||||
import DownloadIcon from '@iconify-vue/mdi/download';
|
||||
import InboxIcon from '@iconify-vue/mdi/inbox-outline';
|
||||
import UploadIcon from '@iconify-vue/mdi/upload';
|
||||
import PostIcon from '@iconify-vue/mdi/post';
|
||||
import BookIcon from '@iconify-vue/mdi/book-open-page-variant';
|
||||
import SignIcon from '@iconify-vue/mdi/sign-real-estate';
|
||||
import StickerIcon from '@iconify-vue/mdi/sticker-circle-outline';
|
||||
import RectangleIcon from '@iconify-vue/mdi/rectangle-outline';
|
||||
import FileIcon from '@iconify-vue/mdi/file';
|
||||
|
||||
// Types
|
||||
interface Printable {
|
||||
id: number;
|
||||
@@ -289,15 +320,15 @@ const getTypeColor = (type: string): string => {
|
||||
return colors[type] || 'grey';
|
||||
};
|
||||
|
||||
const getTypeIcon = (type: string): string => {
|
||||
const icons: Record<string, string> = {
|
||||
poster: 'mdi-post',
|
||||
zine: 'mdi-book-open-page-variant',
|
||||
yardSign: 'mdi-sign-real-estate',
|
||||
sticker: 'mdi-sticker-circle-outline',
|
||||
bumperSticker: 'mdi-rectangle-outline',
|
||||
const getTypeIcon = (type: string) => {
|
||||
const icons: Record<string, any> = {
|
||||
poster: PostIcon,
|
||||
zine: BookIcon,
|
||||
yardSign: SignIcon,
|
||||
sticker: StickerIcon,
|
||||
bumperSticker: RectangleIcon,
|
||||
};
|
||||
return icons[type] || 'mdi-file';
|
||||
return icons[type] || FileIcon;
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
|
||||
@@ -39,12 +39,12 @@
|
||||
<v-divider class="my-12" />
|
||||
|
||||
<h2 class="mb-8">
|
||||
<v-icon class="mr-2" color="primary">mdi-camera-outline</v-icon>
|
||||
<v-icon class="mr-2" color="primary"><CameraOutlineIcon /></v-icon>
|
||||
What do they look like?
|
||||
</h2>
|
||||
<div class="mb-16 text-center">
|
||||
<v-btn size="large" color="primary" to="/identify">
|
||||
<v-icon left class="mr-2">mdi-image-search</v-icon>
|
||||
<v-icon left class="mr-2"><ImageSearchIcon /></v-icon>
|
||||
View ALPR Images
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -76,6 +76,9 @@ import Hero from '@/components/layout/Hero.vue';
|
||||
import Dangers from '@/components/Dangers.vue';
|
||||
import FAQ from '@/components/FAQ.vue';
|
||||
import SimilarProjects from '@/components/SimilarProjects.vue';
|
||||
|
||||
import CameraOutlineIcon from '@iconify-vue/mdi/camera-outline';
|
||||
import ImageSearchIcon from '@iconify-vue/mdi/image-search';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user