You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
15 KiB
433 lines
15 KiB
<template>
|
|
<router-view />
|
|
|
|
<!-- Messages in the upper-right - https://github.com/emmanuelsw/notiwind -->
|
|
<NotificationGroup group="alert">
|
|
<div
|
|
class="fixed top-4 right-4 w-full max-w-sm flex flex-col items-start justify-end"
|
|
>
|
|
<Notification
|
|
v-slot="{ notifications, close }"
|
|
enter="transform ease-out duration-300 transition"
|
|
enter-from="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-4"
|
|
enter-to="translate-y-0 opacity-100 sm:translate-x-0"
|
|
leave="transition ease-in duration-500"
|
|
leave-from="opacity-100"
|
|
leave-to="opacity-0"
|
|
move="transition duration-500"
|
|
move-delay="delay-300"
|
|
>
|
|
<div
|
|
v-for="notification in notifications"
|
|
:key="notification.id"
|
|
class="w-full"
|
|
role="alert"
|
|
>
|
|
<div
|
|
v-if="notification.type === 'toast'"
|
|
class="w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-900/90 text-white rounded-lg shadow-md"
|
|
>
|
|
<div class="w-full px-4 py-3">
|
|
<span class="font-semibold">{{ notification.title }}</span>
|
|
<p class="text-sm">{{ notification.text }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'info'"
|
|
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-100 rounded-lg shadow-md"
|
|
>
|
|
<div
|
|
class="flex items-center justify-center w-12 bg-slate-600 text-slate-100"
|
|
>
|
|
<fa icon="circle-info" class="fa-fw fa-xl"></fa>
|
|
</div>
|
|
|
|
<div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
|
|
<span class="font-semibold">{{ notification.title }}</span>
|
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
|
|
|
<button
|
|
@click="close(notification.id)"
|
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
|
|
>
|
|
<fa icon="xmark" class="fa-fw"></fa>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'success'"
|
|
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-emerald-100 rounded-lg shadow-md"
|
|
>
|
|
<div
|
|
class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100"
|
|
>
|
|
<fa icon="circle-info" class="fa-fw fa-xl"></fa>
|
|
</div>
|
|
|
|
<div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
|
|
<span class="font-semibold">{{ notification.title }}</span>
|
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
|
|
|
<button
|
|
@click="close(notification.id)"
|
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
|
|
>
|
|
<fa icon="xmark" class="fa-fw"></fa>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'warning'"
|
|
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-amber-100 rounded-lg shadow-md"
|
|
>
|
|
<div
|
|
class="flex items-center justify-center w-12 bg-amber-600 text-amber-100"
|
|
>
|
|
<fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
|
|
</div>
|
|
|
|
<div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
|
|
<span class="font-semibold">{{ notification.title }}</span>
|
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
|
|
|
<button
|
|
@click="close(notification.id)"
|
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
|
|
>
|
|
<fa icon="xmark" class="fa-fw"></fa>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'danger'"
|
|
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-rose-100 rounded-lg shadow-md"
|
|
>
|
|
<div
|
|
class="flex items-center justify-center w-12 bg-rose-600 text-rose-100"
|
|
>
|
|
<fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
|
|
</div>
|
|
|
|
<div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
|
|
<span class="font-semibold">{{ notification.title }}</span>
|
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
|
|
|
<button
|
|
@click="close(notification.id)"
|
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
|
|
>
|
|
<fa icon="xmark" class="fa-fw"></fa>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Notification>
|
|
</div>
|
|
</NotificationGroup>
|
|
|
|
<!--
|
|
This "group" of "modal" is the prompt for an answer.
|
|
Set "type" as follows: "confirm" for yes/no, and "notification" ones: "-permission", "-mute", "-off"
|
|
-->
|
|
<NotificationGroup group="modal">
|
|
<div class="fixed z-[100] top-0 inset-x-0 w-full">
|
|
<Notification
|
|
v-slot="{ notifications, close }"
|
|
enter="transform ease-out duration-300 transition"
|
|
enter-from="translate-y-2 opacity-0 sm:translate-y-4"
|
|
enter-to="translate-y-0 opacity-100 sm:translate-y-0"
|
|
leave="transition ease-in duration-500"
|
|
leave-from="opacity-100"
|
|
leave-to="opacity-0"
|
|
move="transition duration-500"
|
|
move-delay="delay-300"
|
|
>
|
|
<!-- see NotificationIface in constants/app.ts -->
|
|
<div
|
|
v-for="notification in notifications"
|
|
:key="notification.id"
|
|
class="w-full"
|
|
role="alert"
|
|
>
|
|
<!--
|
|
Type of "confirm" will post a message.
|
|
With onYes function, show a "Yes" button to call that function.
|
|
With onNo function, show a "No" button to call that function,
|
|
and pass it state of "askAgain" field shown if you set promptToStopAsking.
|
|
-->
|
|
<div
|
|
v-if="notification.type === 'confirm'"
|
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
|
>
|
|
<div
|
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
>
|
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
|
<span class="font-semibold text-lg">
|
|
{{ notification.title }}
|
|
</span>
|
|
<p class="text-sm mb-2">{{ notification.text }}</p>
|
|
|
|
<button
|
|
v-if="notification.onYes"
|
|
@click="
|
|
notification.onYes();
|
|
close(notification.id);
|
|
"
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
Yes{{
|
|
notification.yesText ? ", " + notification.yesText : ""
|
|
}}
|
|
</button>
|
|
|
|
<button
|
|
v-if="notification.onNo"
|
|
@click="
|
|
notification.onNo(stopAsking);
|
|
close(notification.id);
|
|
stopAsking = false; // reset value
|
|
"
|
|
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
No{{ notification.noText ? ", " + notification.noText : "" }}
|
|
</button>
|
|
|
|
<label
|
|
v-if="notification.promptToStopAsking && notification.onNo"
|
|
for="toggleStopAsking"
|
|
class="flex items-center justify-between cursor-pointer my-4"
|
|
@click="stopAsking = !stopAsking"
|
|
>
|
|
<!-- label -->
|
|
<span class="ml-2">... and do not ask again.</span>
|
|
<!-- toggle -->
|
|
<div class="relative ml-2">
|
|
<!-- input -->
|
|
<input
|
|
type="checkbox"
|
|
v-model="stopAsking"
|
|
name="stopAsking"
|
|
class="sr-only"
|
|
/>
|
|
<!-- line -->
|
|
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
|
<!-- dot -->
|
|
<div
|
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
|
></div>
|
|
</div>
|
|
</label>
|
|
|
|
<button
|
|
@click="
|
|
notification.onCancel
|
|
? notification.onCancel(stopAsking)
|
|
: null;
|
|
close(notification.id);
|
|
stopAsking = false; // reset value for next time they open this modal
|
|
"
|
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
>
|
|
{{ notification.onYes ? "Cancel" : "Close" }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'notification-mute'"
|
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
|
>
|
|
<div
|
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
>
|
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
|
<p class="text-lg mb-4">Mute app notifications:</p>
|
|
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
For 1 Day
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
For 2 Days
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
For 1 Week
|
|
</button>
|
|
<button
|
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
Until I turn it back on
|
|
</button>
|
|
<button
|
|
@click="close(notification.id)"
|
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="notification.type === 'notification-off'"
|
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
|
>
|
|
<div
|
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
>
|
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
|
<p class="text-lg mb-4">
|
|
Would you like to <b>turn off</b> this notification?
|
|
</p>
|
|
|
|
<button
|
|
@click="
|
|
close(notification.id);
|
|
turnOffNotifications(notification);
|
|
"
|
|
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
|
>
|
|
Turn Off Notification
|
|
</button>
|
|
<button
|
|
@click="close(notification.id)"
|
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
>
|
|
Leave it On
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Notification>
|
|
</div>
|
|
</NotificationGroup>
|
|
</template>
|
|
|
|
<style></style>
|
|
|
|
<script lang="ts">
|
|
import { Vue, Component } from "vue-facing-decorator";
|
|
|
|
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "@/db/index";
|
|
import { NotificationIface } from "./constants/app";
|
|
|
|
@Component
|
|
export default class App extends Vue {
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
|
|
stopAsking = false;
|
|
|
|
truncateLongWords(sentence: string) {
|
|
return sentence
|
|
.split(" ")
|
|
.map((word) => (word.length > 30 ? word.slice(0, 30) + "..." : word))
|
|
.join(" ");
|
|
}
|
|
|
|
async turnOffNotifications(notification: NotificationIface) {
|
|
let subscription: object | null = null;
|
|
|
|
let allGoingOff = false;
|
|
const settings = await retrieveSettingsForActiveAccount();
|
|
const notifyingNewActivity = !!settings?.notifyingNewActivityTime;
|
|
const notifyingReminder = !!settings?.notifyingReminderTime;
|
|
if (!notifyingNewActivity || !notifyingReminder) {
|
|
// the other notification is already off, so fully unsubscribe now
|
|
allGoingOff = true;
|
|
}
|
|
|
|
await navigator.serviceWorker?.ready
|
|
.then((registration) => {
|
|
return registration.pushManager.getSubscription();
|
|
})
|
|
.then(async (subscript: PushSubscription | null) => {
|
|
if (subscript) {
|
|
subscription = subscript.toJSON();
|
|
if (allGoingOff) {
|
|
await subscript.unsubscribe();
|
|
}
|
|
} else {
|
|
logConsoleAndDb("Subscription object is not available.");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
logConsoleAndDb(
|
|
"Push provider server communication failed: " + JSON.stringify(error),
|
|
true,
|
|
);
|
|
});
|
|
|
|
if (!subscription) {
|
|
// there is no endpoint or auth for the server to compare, so we're done
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "info",
|
|
title: "Finished",
|
|
text: "Notifications are off.", // a different message so I know there are none stored
|
|
},
|
|
5000,
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// clone in order to get only the properties and allow stringify to work
|
|
const serverSubscription = {
|
|
...subscription,
|
|
};
|
|
if (!allGoingOff) {
|
|
serverSubscription["notifyType"] = notification.title;
|
|
}
|
|
const pushServerSuccess = await fetch("/web-push/unsubscribe", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(serverSubscription),
|
|
})
|
|
.then((response) => {
|
|
return response.ok;
|
|
})
|
|
.catch((error) => {
|
|
logConsoleAndDb(
|
|
"Push server communication failed: " + JSON.stringify(error),
|
|
true,
|
|
);
|
|
return false;
|
|
});
|
|
|
|
let message;
|
|
if (pushServerSuccess) {
|
|
message = "Notification is off.";
|
|
} else {
|
|
message = "Notification is still on. Try to turn it off again.";
|
|
}
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "info",
|
|
title: "Finished",
|
|
text: message,
|
|
},
|
|
5000,
|
|
);
|
|
|
|
if (notification.callback) {
|
|
// it's OK if the local notifications are still on (especially if the other notification is on)
|
|
notification.callback(pushServerSuccess);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|