Skip to content

Commit

Permalink
fix: use a progressing time provider for ui computeds
Browse files Browse the repository at this point in the history
  • Loading branch information
valeriansaliou committed Dec 5, 2024
1 parent c4f689a commit 848305a
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 14 deletions.
15 changes: 12 additions & 3 deletions src/cards/inbox/MessageAuthor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ import { Room, JID, ParticipantInfo } from "@prose-im/prose-sdk-js";
import Store from "@/store";
import { InboxEntryMessage } from "@/store/tables/inbox";

// PROJECT: COMPOSABLES
import { useTimerMinutes } from "@/composables/timer";

export default {
name: "MessageAuthor",

Expand Down Expand Up @@ -125,6 +128,14 @@ export default {
}
},

setup() {
const { date } = useTimerMinutes();

return {
localDateMinutes: date
};
},

computed: {
message(): InboxEntryMessage | void {
return Store.$inbox.getMessage(this.room.id, this.messageId);
Expand Down Expand Up @@ -203,10 +214,8 @@ export default {
this.profile?.information?.location.timezone || null;

if (profileTimezone !== null) {
const nowDate = new Date();

return `${this.$filters.date.localTime(
nowDate,
this.localDateMinutes,
profileTimezone.offset
)} local time`;
}
Expand Down
24 changes: 16 additions & 8 deletions src/components/inbox/InboxBanner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { JID, Room } from "@prose-im/prose-sdk-js";

// PROJECT: COMPOSABLES
import { useEvents } from "@/composables/events";
import { useTimerMinutes } from "@/composables/timer";

// PROJECT: STORES
import Store from "@/store";
Expand Down Expand Up @@ -90,6 +91,18 @@ export default {
}
},

setup() {
// Export a local date that monotonically updates every minute
// Notice: this is required, since a Vue computed on a Date instance will \
// only return once, and be treated as a static value afterwards. \
// Meaning the date value would not progress as time passes.
const { date } = useTimerMinutes();

return {
localDateMinutes: date
};
},

data() {
return {
// --> STATE <--
Expand Down Expand Up @@ -156,7 +169,7 @@ export default {
// Apply offset to date (in minutes)
// Notice: create new date object, as not to mutate the provided one.
const userDate = new Date(
this.localDate.getTime() +
this.localDateMinutes.getTime() +
userTimezone.offset * MINUTE_TO_MILLISECONDS
);

Expand All @@ -167,7 +180,7 @@ export default {
userTimeHour < CONSIDER_TIME_ASLEEP_BEFORE
) {
return this.$filters.date.localTime(
this.localDate,
this.localDateMinutes,
userTimezone.offset
);
}
Expand All @@ -176,15 +189,10 @@ export default {
return null;
},

localDate(): Date {
// Return current local date (for local environment)
return new Date();
},

localTimezoneOffset(): number {
// Return current local TZO (for local environment)
// Important: negate result since JS returns inverted TZOs.
return -this.localDate.getTimezoneOffset();
return -this.localDateMinutes.getTimezoneOffset();
},

session(): typeof Store.$session {
Expand Down
22 changes: 19 additions & 3 deletions src/components/inbox/InboxDetailsUserInformation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ import { PropType } from "vue";
import { JID, Availability } from "@prose-im/prose-sdk-js";
import { getCountryCode, getCountryName } from "crisp-countries-languages";

// PROJECT: COMPOSABLES
import { useTimerMinutes } from "@/composables/timer";

// PROJECT: STORES
import Store from "@/store";

Expand Down Expand Up @@ -98,6 +101,18 @@ export default {
}
},

setup() {
// Export a local date that monotonically updates every minute
// Notice: this is required, since a Vue computed on a Date instance will \
// only return once, and be treated as a static value afterwards. \
// Meaning the date value would not progress as time passes.
const { date } = useTimerMinutes();

return {
localDateMinutes: date
};
},

data() {
return {
// --> DATA <--
Expand Down Expand Up @@ -149,11 +164,12 @@ export default {
userCountry = this.profile.information.location.country || null;

if (userTimezone !== null) {
const nowDate = new Date();

entries.push({
id: "timezone",
title: this.$filters.date.localTime(nowDate, userTimezone.offset),
title: this.$filters.date.localTime(
this.localDateMinutes,
userTimezone.offset
),
icon: "clock.fill"
});
}
Expand Down
89 changes: 89 additions & 0 deletions src/composables/timer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* This file is part of prose-app-web
*
* Copyright 2024, Prose Foundation
*/

/**************************************************************************
* IMPORTS
* ************************************************************************* */

// NPM
import { Ref, ref, onMounted, onBeforeUnmount } from "vue";

/**************************************************************************
* COMPOSABLE
* ************************************************************************* */

function useTimerMinutes(): {
date: Ref<Date>;
} {
// --> INTERNALS <--

let timer = null as null | ReturnType<typeof setTimeout>;

// --> DATA <--

const date = ref(new Date());

// --> METHODS <--

const scheduleNextTick = async function () {
if (timer === null) {
// Acquire current timestamp
const nowTime = Date.now();

// Acquire date at next minute, at second zero (clone current date)
const nextMinuteDate = new Date(nowTime);

nextMinuteDate.setSeconds(0);
nextMinuteDate.setMinutes(nextMinuteDate.getMinutes() + 1);

// Calculate time remaining to next minute
const timeToNextMinute = nextMinuteDate.getTime() - nowTime;

// Schedule next timer (to fire at next minute, at second zero)
// Important: do not use a reliable timeout scheduler here, since the \
// date output value is solely used by UI and therefore we want to \
// avoid triggering UI digests when the application is put in the \
// background.
timer = setTimeout(() => {
timer = null;

// Update date
// Important: do not assign 'nextMinuteDate' here, since the \
// JavaScript VM might have throttled this timer and thus the date \
// might have became stale relative to now. Always create a new date \
// object for this reason.
date.value = new Date();

// Schedule next timer tick
scheduleNextTick();
}, timeToNextMinute);
}
};

// --> LIFECYCLE <--

onMounted(() => {
// Schedule first timer tick
scheduleNextTick();
});

onBeforeUnmount(() => {
// Unschedule timer? (if any)
if (timer !== null) {
clearTimeout(timer);

timer = null;
}
});

return { date };
}

/**************************************************************************
* EXPORTS
* ************************************************************************* */

export { useTimerMinutes };

0 comments on commit 848305a

Please sign in to comment.