Skip to content

Commit

Permalink
feat: allow the planning of future meeting schedule changes (#3898)
Browse files Browse the repository at this point in the history
* feat: plan schedule changes

* fix: logic

* fix: tweak strings

* feat: add subgroup to meetingScheduleChange settings

* fix: add try-catches and folder existence checks to cleanup
  • Loading branch information
mtdvlpr authored Jan 15, 2025
1 parent c0daf2f commit 141405b
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 28 deletions.
12 changes: 1 addition & 11 deletions src/components/dialog/DialogCacheClear.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,9 @@
<script setup lang="ts">
import type { CacheFile } from 'src/types';
import { storeToRefs } from 'pinia';
import { updateLookupPeriod } from 'src/helpers/date';
import { errorCatcher } from 'src/helpers/error-catcher';
import { removeEmptyDirs } from 'src/utils/fs';
import { useCurrentStateStore } from 'stores/current-state';
import { useJwStore } from 'stores/jw';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
Expand All @@ -49,12 +46,6 @@ const props = defineProps<{
unusedParentDirectories: Record<string, number>;
}>();
const jwStore = useJwStore();
const { lookupPeriod } = storeToRefs(jwStore);
const currentState = useCurrentStateStore();
const { currentCongregation } = storeToRefs(currentState);
const open = defineModel<boolean>({ default: false });
const cacheClearType = defineModel<'' | 'all' | 'smart'>('cacheClearType', {
required: true,
Expand Down Expand Up @@ -108,8 +99,7 @@ const deleteCacheFiles = async (type = '') => {
// concurrency: 5,
// });
if (type === 'all') {
lookupPeriod.value[currentCongregation.value] = [];
updateLookupPeriod();
updateLookupPeriod(true);
}
cancelDeleteCacheFiles();
} catch (error) {
Expand Down
56 changes: 56 additions & 0 deletions src/constants/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,56 @@ export const settingsDefinitions: SettingsItems = {
type: 'date',
unless: 'disableMediaFetching',
},
meetingScheduleChangeDate: {
group: 'congregationMeetings',
type: 'date',
options: ['futureDate'],
subgroup: 'meetingScheduleChange',
unless: 'disableMediaFetching',
},
meetingScheduleChangeOnce: {
group: 'congregationMeetings',
depends: 'meetingScheduleChangeDate',
subgroup: 'meetingScheduleChange',
type: 'toggle',
unless: 'disableMediaFetching',
},
meetingScheduleChangeMwDay: {
group: 'congregationMeetings',
depends: 'meetingScheduleChangeDate',
list: 'days',
rules: ['regular'],
subgroup: 'meetingScheduleChange',
type: 'list',
unless: 'disableMediaFetching',
},
meetingScheduleChangeMwStartTime: {
group: 'congregationMeetings',
depends: 'meetingScheduleChangeDate',
options: ['meetingTime'],
rules: ['regular'],
subgroup: 'meetingScheduleChange',
type: 'time',
unless: 'disableMediaFetching',
},
meetingScheduleChangeWeDay: {
group: 'congregationMeetings',
depends: 'meetingScheduleChangeDate',
list: 'days',
rules: ['regular'],
subgroup: 'meetingScheduleChange',
type: 'list',
unless: 'disableMediaFetching',
},
meetingScheduleChangeWeStartTime: {
group: 'congregationMeetings',
depends: 'meetingScheduleChangeDate',
options: ['meetingTime'],
rules: ['regular'],
subgroup: 'meetingScheduleChange',
type: 'time',
unless: 'disableMediaFetching',
},
// Media Retrieval and Playback
enableMediaDisplayButton: {
group: 'mediaRetrievalPlayback',
Expand Down Expand Up @@ -377,6 +427,12 @@ export const defaultSettings: SettingsValues = {
localAppLang: 'en',
maxRes: '720p',
mediaAutoExportFolder: '',
meetingScheduleChangeDate: null,
meetingScheduleChangeMwDay: null,
meetingScheduleChangeMwStartTime: null,
meetingScheduleChangeOnce: false,
meetingScheduleChangeWeDay: null,
meetingScheduleChangeWeStartTime: null,
musicVolume: 100,
mwDay: null,
mwStartTime: null,
Expand Down
6 changes: 5 additions & 1 deletion src/helpers/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const cleanCongregationRecord = (
record: Partial<Record<string, unknown>>,
congIds: Set<string>,
) => {
if (!record || !congIds) return;
Object.keys(record).forEach((congId) => {
if (!congIds.has(congId)) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
Expand All @@ -29,6 +30,7 @@ export const cleanPersistedStores = () => {
};

const cleanCongregationFolders = async (root: string, congIds: Set<string>) => {
if (!root || !congIds || !(await window.electronApi.fs.exists(root))) return;
const folders = await window.electronApi.fs.readdir(root);
folders.forEach((f) => {
if (!congIds.has(f)) {
Expand All @@ -38,6 +40,8 @@ const cleanCongregationFolders = async (root: string, congIds: Set<string>) => {
};

const cleanPublicTalkPubs = async (folder: string, congIds: Set<string>) => {
if (!folder || !congIds || !(await window.electronApi.fs.exists(folder)))
return;
const files = await window.electronApi.fs.readdir(folder);
files.forEach((f) => {
if (!f.startsWith('S-34mp_')) return;
Expand All @@ -48,7 +52,7 @@ const cleanPublicTalkPubs = async (folder: string, congIds: Set<string>) => {
};

const cleanDateFolders = async (root?: string) => {
if (!root) return;
if (!root || !(await window.electronApi.fs.exists(root))) return;

const folders = await window.electronApi.fs.readdir(root);
folders.forEach((f) => {
Expand Down
84 changes: 78 additions & 6 deletions src/helpers/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
formatDate,
getDateDiff,
getSpecificWeekday,
isInPast,
} from 'src/utils/date';
import { useCurrentStateStore } from 'stores/current-state';
import { useJwStore } from 'stores/jw';
Expand Down Expand Up @@ -51,6 +52,21 @@ export function isCoWeek(lookupDate: Date) {
}
}

const shouldUseChangedMeetingSchedule = (lookupDate: Date | string) => {
lookupDate = dateFromString(lookupDate);
if (isInPast(lookupDate)) return false;

const { currentSettings } = useCurrentStateStore();
const changedDate = currentSettings?.meetingScheduleChangeDate;
const changeOnce = currentSettings?.meetingScheduleChangeOnce;

return (
changedDate &&
getDateDiff(lookupDate, changedDate, 'days') >= 0 &&
(!changeOnce || getDateDiff(lookupDate, changedDate, 'days') < 7)
);
};

export const isMwMeetingDay = (lookupDate?: Date) => {
try {
const currentState = useCurrentStateStore();
Expand All @@ -63,6 +79,11 @@ export const isMwMeetingDay = (lookupDate?: Date) => {
currentState.currentSettings?.coWeek ?? undefined,
);
return datesAreSame(coWeekTuesday, lookupDate);
} else if (shouldUseChangedMeetingSchedule(lookupDate)) {
return (
(currentState.currentSettings?.meetingScheduleChangeMwDay ??
currentState.currentSettings?.mwDay) === getWeekDay(lookupDate)
);
} else {
return currentState.currentSettings?.mwDay === getWeekDay(lookupDate);
}
Expand All @@ -78,7 +99,14 @@ export const isWeMeetingDay = (lookupDate?: Date) => {
if (!lookupDate || currentState.currentSettings?.disableMediaFetching)
return false;
lookupDate = dateFromString(lookupDate);
return currentState.currentSettings?.weDay === getWeekDay(lookupDate);

const changedWeDay =
currentState.currentSettings?.meetingScheduleChangeWeDay;
if (changedWeDay && shouldUseChangedMeetingSchedule(lookupDate)) {
return changedWeDay === getWeekDay(lookupDate);
} else {
return currentState.currentSettings?.weDay === getWeekDay(lookupDate);
}
} catch (error) {
errorCatcher(error);
return false;
Expand All @@ -87,8 +115,41 @@ export const isWeMeetingDay = (lookupDate?: Date) => {

export function updateLookupPeriod(reset = false) {
try {
const { currentCongregation } = useCurrentStateStore();
if (!currentCongregation) return;
const { currentCongregation, currentSettings } = useCurrentStateStore();
if (!currentCongregation || !currentSettings) return;

if (
currentSettings.meetingScheduleChangeDate &&
((!currentSettings.meetingScheduleChangeOnce &&
isInPast(currentSettings.meetingScheduleChangeDate, true)) ||
(currentSettings.meetingScheduleChangeOnce &&
isInPast(
addToDate(currentSettings.meetingScheduleChangeDate, { day: 7 }),
true,
)))
) {
// Update meeting schedule
if (!currentSettings.meetingScheduleChangeOnce) {
currentSettings.mwDay =
currentSettings.meetingScheduleChangeMwDay ?? currentSettings.mwDay;
currentSettings.mwStartTime =
currentSettings.meetingScheduleChangeMwStartTime ??
currentSettings.mwStartTime;
currentSettings.weDay =
currentSettings.meetingScheduleChangeWeDay ?? currentSettings.weDay;
currentSettings.weStartTime =
currentSettings.meetingScheduleChangeWeStartTime ??
currentSettings.weStartTime;
}

// Clear meeting schedule change settings
currentSettings.meetingScheduleChangeDate = null;
currentSettings.meetingScheduleChangeOnce = false;
currentSettings.meetingScheduleChangeMwDay = null;
currentSettings.meetingScheduleChangeMwStartTime = null;
currentSettings.meetingScheduleChangeWeDay = null;
currentSettings.meetingScheduleChangeWeStartTime = null;
}

const { lookupPeriod } = useJwStore();
if (!lookupPeriod[currentCongregation]) {
Expand Down Expand Up @@ -167,9 +228,20 @@ export const remainingTimeBeforeMeetingStart = () => {
if (meetingDay) {
const now = new Date();
const weMeeting = currentState.selectedDateObject?.meeting === 'we';
const meetingStartTime = weMeeting
? currentState.currentSettings?.weStartTime
: currentState.currentSettings?.mwStartTime;
const meetingStartTimes = shouldUseChangedMeetingSchedule(now)
? {
mw:
currentState.currentSettings?.meetingScheduleChangeMwStartTime ??
currentState.currentSettings?.mwStartTime,
we:
currentState.currentSettings?.meetingScheduleChangeWeStartTime ??
currentState.currentSettings?.weStartTime,
}
: {
mw: currentState.currentSettings?.mwStartTime,
we: currentState.currentSettings?.weStartTime,
};
const meetingStartTime = meetingStartTimes[weMeeting ? 'we' : 'mw'];
if (!meetingStartTime) return 0;
const [hours, minutes] = meetingStartTime.split(':').map(Number);
const meetingStartDateTime = new Date(now);
Expand Down
6 changes: 6 additions & 0 deletions src/helpers/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export const buildNewPrefsObject = (oldPrefs: OldAppConfig) => {
: oldPrefs.app?.localAppLang) as LanguageValue) || 'en',
maxRes: oldPrefs.media?.maxRes || '720p',
mediaAutoExportFolder: oldPrefs.app?.localOutputPath || '',
meetingScheduleChangeDate: null,
meetingScheduleChangeMwDay: null,
meetingScheduleChangeMwStartTime: null,
meetingScheduleChangeOnce: false,
meetingScheduleChangeWeDay: null,
meetingScheduleChangeWeStartTime: null,
musicVolume: oldPrefs.meeting?.musicVolume || 100,
mwDay: oldPrefs.meeting?.mwDay?.toString() || '',
mwStartTime: oldPrefs.meeting?.mwStartTime?.toString() || '',
Expand Down
21 changes: 17 additions & 4 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@
"choose-a-folder": "Choose a folder",
"choose-a-song": "Choose a song",
"choose-an-image": "Choose an image",
"circuit-overseer": "Circuit Overseer",
"clear": "Clear",
"clear-cache": "Clear the cache",
"clicking-it-will-allow-you-to-start-and-stop-the-playback-of-background-music-music-will-start-playing-automatically-before-a-meeting-is-scheduled-to-start-when-m-is-launched-and-will-also-stop-automatically-before-the-meeting-starts-however-background-music-playback-will-need-to-be-manually-started-after-the-closing-prayer-using-this-button": "Clicking it will allow you to start and stop the playback of background music. Music will start playing automatically before a meeting is scheduled to start when M³ is launched, and will also stop automatically before the meeting starts. However, background music playback will need to be manually started after the closing prayer, using this button.",
"clicking-it-will-allow-you-to-temporarily-hide-the-media-and-yeartext-and-reveal-the-zoom-participants-underneath-once-the-zoom-part-is-over-you-can-show-the-yeartext-again-using-the-same-button": "Clicking it will allow you to temporarily hide the media and yeartext, and reveal the Zoom participants underneath. Once the Zoom part is over, you can show the yeartext again using the same button.",
"clicking-the-close-button-again-will-close-app": "Clicking the same button again will quit M³.",
"close": "Close",
"circuit-overseer": "Circuit Overseer",
"coWeek": "Next planned visit of the circuit overseer",
"coWeek-explain": "If entered, M³ will automatically move the midweek meeting to Tuesday on that week. It will also skip media for the Congregation Bible Study, and skip the closing songs for both the midweek and weekend meetings.",
"collapse-sidebar": "Collapse sidebar",
Expand Down Expand Up @@ -216,6 +216,19 @@
"mediaRetrievalAndPlayback": "Media retrieval and playback",
"mediaRetrievalAndPlaybackDescription": "Configure meeting media download and playback settings.",
"meeting-media-manager": "Meeting Media Manager",
"meetingScheduleChange": "Meeting schedule change",
"meetingScheduleChangeDate": "Date of schedule change",
"meetingScheduleChangeDate-explain": "Enter the date when the new meeting schedule should take effect. All meetings after this date will follow the new schedule, and the old schedule will be permanently replaced.",
"meetingScheduleChangeMwDay": "New midweek meeting day",
"meetingScheduleChangeMwDay-explain": "Select the new day for the midweek meeting. Leave blank if the day will not change.",
"meetingScheduleChangeMwStartTime": "New midweek meeting time",
"meetingScheduleChangeMwStartTime-explain": "Select the new start time for the midweek meeting. Leave blank if the time will not change.",
"meetingScheduleChangeOnce": "Apply schedule change to one meeting only",
"meetingScheduleChangeOnce-explain": "Enable this to apply the schedule change to only one meeting on the specified date. This is useful for one-time changes, such as during a circuit overseer's visit to a neighboring congregation. Otherwise, the schedule will be updated for all meetings as of the specified date.",
"meetingScheduleChangeWeDay": "New weekend meeting day",
"meetingScheduleChangeWeDay-explain": "Select the new day for the weekend meeting. Leave blank if the day will not change.",
"meetingScheduleChangeWeStartTime": "New weekend meeting time",
"meetingScheduleChangeWeStartTime-explain": "Select the new start time for the weekend meeting. Leave blank if the time will not change.",
"midweek-meeting": "Midweek meeting",
"months-long": "January_February_March_April_May_June_July_August_September_October_November_December",
"months-short": "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec",
Expand Down Expand Up @@ -244,12 +257,12 @@
"none": "None",
"not-playing": "Not playing",
"notice-the-yeartext-is-now-being-displayed-on-the-external-monitor-but-lets-keep-going": "You'll notice the yeartext is now being displayed on the external monitor! But let's keep going.",
"obs-studio-disconnected-banner": "M³ is unable to connect to OBS Studio. Please ensure that OBS Studio is running and configured correctly.",
"obs-studio-integration": "OBS Studio integration",
"obs-studio-is-a-free-app-used-to-manage-camera-and-video-feeds-in-many-kingdom-halls": "OBS Studio is a free app used to manage camera and video feeds in many Kingdom Halls.",
"obs-studio-media-scene": "OBS Studio media scene",
"obs-studio-port-and-password": "OBS Studio port and password",
"obs-studio-stage-scene": "OBS Studio stage scene",
"obs-studio-disconnected-banner": "M³ is unable to connect to OBS Studio. Please ensure that OBS Studio is running and configured correctly.",
"obs.connected": "OBS Studio: Connected",
"obs.connecting": "OBS Studio: Connecting",
"obs.disconnected": "OBS Studio: Disconnected",
Expand Down Expand Up @@ -343,6 +356,7 @@
"shortcutMediaWindow-explain": "Shortcut to quickly hide or show the media window.",
"shortcutMusic": "Start/stop background music",
"shortcutMusic-explain": "Shortcut to start or stop the background music.",
"show-all-media": "Show all media",
"show-all-settings": "Show all settings",
"show-hidden-media": "Show hidden media",
"show-hidden-media-explain": "This will show any media that was previously hidden for the currently selected day.",
Expand Down Expand Up @@ -414,6 +428,5 @@
"yes": "Yes",
"you-can-navigate-the-website-before-presenting-it": "If necessary, you can navigate to a specific page before presenting it to the audience. When you're ready, press the \"Start Mirroring\" button on the top right corner of this window. What you see in the website window will now be displayed in the media window for the audience to see.",
"zoom-in": "Zoom in",
"zoom-out": "Zoom out",
"show-all-media": "Show all media"
"zoom-out": "Zoom out"
}
4 changes: 4 additions & 0 deletions src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ watch(
currentSettings.value?.mwDay,
currentSettings.value?.weDay,
currentSettings.value?.coWeek,
currentSettings.value?.meetingScheduleChangeDate,
currentSettings.value?.meetingScheduleChangeOnce,
currentSettings.value?.meetingScheduleChangeMwDay,
currentSettings.value?.meetingScheduleChangeWeDay,
currentSettings.value?.disableMediaFetching,
],
(
Expand Down
8 changes: 7 additions & 1 deletion src/types/settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export type SettingsItemListKey =
| 'obsScenes'
| 'resolutions';

export type SettingsItemOption = 'coTuesdays' | 'meetingTime';
export type SettingsItemOption = 'coTuesdays' | 'futureDate' | 'meetingTime';
export type SettingsItemRule = 'notEmpty' | 'portNumber' | 'regular';

export type SettingsItems = Record<keyof SettingsValues, SettingsItem>;
Expand Down Expand Up @@ -192,6 +192,12 @@ export interface SettingsValues {
localAppLang: LanguageValue;
maxRes: MaxRes;
mediaAutoExportFolder: string;
meetingScheduleChangeDate: null | string;
meetingScheduleChangeMwDay: null | string;
meetingScheduleChangeMwStartTime: null | string;
meetingScheduleChangeOnce: boolean;
meetingScheduleChangeWeDay: null | string;
meetingScheduleChangeWeStartTime: null | string;
musicVolume: number;
mwDay: null | string;
mwStartTime: null | string;
Expand Down
Loading

0 comments on commit 141405b

Please sign in to comment.