From 21536287678d3bf6482bcccd9e2243077afe3115 Mon Sep 17 00:00:00 2001 From: Samuel Meuli Date: Thu, 23 Jan 2020 17:05:07 +0700 Subject: [PATCH] Add "Go to date" dialog Closes #57 --- src/main/i18n/translations/de.ts | 2 + src/main/i18n/translations/en.ts | 2 + src/main/ipcMain/senders.ts | 8 +-- src/main/menu/menu.ts | 1 + src/main/menu/menus/file.ts | 4 +- src/main/menu/menus/view.ts | 9 +++ src/main/menu/preferencesItem.ts | 4 +- src/renderer/components/App.tsx | 3 + .../go-to-date-overlay/GoToDateOverlay.tsx | 69 +++++++++++++++++++ .../GoToDateOverlayContainer.tsx | 20 ++++++ .../electron/ipcRenderer/listeners.ts | 8 +-- src/renderer/types.ts | 2 +- src/shared/types.ts | 2 + 13 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 src/renderer/components/overlays/go-to-date-overlay/GoToDateOverlay.tsx create mode 100644 src/renderer/components/overlays/go-to-date-overlay/GoToDateOverlayContainer.tsx diff --git a/src/main/i18n/translations/de.ts b/src/main/i18n/translations/de.ts index 1a425a2e..70b53393 100644 --- a/src/main/i18n/translations/de.ts +++ b/src/main/i18n/translations/de.ts @@ -31,6 +31,7 @@ const translationsDe: Partial = { // Menu (app-specific) "export": "Exportieren", "export-to-format": "Als {format} exportieren", + "go-to-date": "Zu Datum gehen", "import": "Importieren", "import-from-format": "Von {format} importieren", "license": "Lizenz", @@ -70,6 +71,7 @@ const translationsDe: Partial = { "allow-future-entries": "Einträge in der Zukunft erlauben", "diary-entries": "Tagebucheinträge", "no": "Nein", + "ok": "OK", "reset-diary": "Tagebuch zurücksetzen", "reset-diary-confirm": "Ja, ich bin mir sicher", "reset-diary-msg": "Bist du sicher, dass du dein Tagebuch zurücksetzen möchtest? Diese Aktion wird all deine Einträge unwiderruflich löschen.", diff --git a/src/main/i18n/translations/en.ts b/src/main/i18n/translations/en.ts index 37618feb..4a23eb93 100644 --- a/src/main/i18n/translations/en.ts +++ b/src/main/i18n/translations/en.ts @@ -31,6 +31,7 @@ const translationsEn: Translations = { // Menu (app-specific) "export": "Export", "export-to-format": "Export to {format}", + "go-to-date": "Go to Date", "import": "Import", "import-from-format": "Import from {format}", "license": "License", @@ -70,6 +71,7 @@ const translationsEn: Translations = { "allow-future-entries": "Allow entries in the future", "diary-entries": "Diary entries", "no": "No", + "ok": "OK", "reset-diary": "Reset diary", "reset-diary-confirm": "Yes, I am sure", "reset-diary-msg": "Are you sure you want to reset your diary? This will delete all of your content. The data cannot be restored.", diff --git a/src/main/ipcMain/senders.ts b/src/main/ipcMain/senders.ts index 0e42670c..1c14e0f6 100644 --- a/src/main/ipcMain/senders.ts +++ b/src/main/ipcMain/senders.ts @@ -62,10 +62,6 @@ export const lock = (): void => { // Overlays -export const showPrefOverlay = (): void => { - getWindow().webContents.send("showPrefOverlay"); -}; - -export const showStatsOverlay = (): void => { - getWindow().webContents.send("showStatsOverlay"); +export const openOverlay = (overlayType: OverlayType): void => { + getWindow().webContents.send("openOverlay", overlayType); }; diff --git a/src/main/menu/menu.ts b/src/main/menu/menu.ts index 7245cbcd..208684fe 100644 --- a/src/main/menu/menu.ts +++ b/src/main/menu/menu.ts @@ -8,6 +8,7 @@ const DISABLED_MENU_ITEMS = [ "exportMd", "exportPdf", "exportTxtDayOne", + "goToDate", "importJsonDayOne", "importJsonJrnl", "importJsonMiniDiary", diff --git a/src/main/menu/menus/file.ts b/src/main/menu/menus/file.ts index a54450df..89fd0f23 100644 --- a/src/main/menu/menus/file.ts +++ b/src/main/menu/menus/file.ts @@ -11,7 +11,7 @@ import { importJsonMiniDiary, importTxtDayOne, lock, - showStatsOverlay, + openOverlay, } from "../../ipcMain/senders"; export default function getFileMenu(): MenuItemConstructorOptions { @@ -99,7 +99,7 @@ export default function getFileMenu(): MenuItemConstructorOptions { label: translate("statistics"), id: "statistics", click(): void { - showStatsOverlay(); + openOverlay("statistics"); }, }, ], diff --git a/src/main/menu/menus/view.ts b/src/main/menu/menus/view.ts index 220cc410..72f1f319 100644 --- a/src/main/menu/menus/view.ts +++ b/src/main/menu/menus/view.ts @@ -6,12 +6,21 @@ import { setDaySelectedPrevious, setMonthSelectedNext, setMonthSelectedPrevious, + openOverlay, } from "../../ipcMain/senders"; export default function getViewMenu(): MenuItemConstructorOptions { return { label: translate("view"), submenu: [ + { + label: `${translate("go-to-date")}…`, + id: "goToDate", + click(): void { + openOverlay("go-to-date"); + }, + }, + { type: "separator" }, { label: translate("previous-day"), id: "previousDay", diff --git a/src/main/menu/preferencesItem.ts b/src/main/menu/preferencesItem.ts index 028fd85d..9c0d0f1b 100644 --- a/src/main/menu/preferencesItem.ts +++ b/src/main/menu/preferencesItem.ts @@ -1,7 +1,7 @@ import { MenuItemConstructorOptions } from "electron"; import { translate } from "../i18n/i18n"; -import { showPrefOverlay } from "../ipcMain/senders"; +import { openOverlay } from "../ipcMain/senders"; export default function getPreferencesItem(): MenuItemConstructorOptions { return { @@ -9,7 +9,7 @@ export default function getPreferencesItem(): MenuItemConstructorOptions { id: "preferences", accelerator: "CmdOrCtrl+,", click(): void { - showPrefOverlay(); + openOverlay("preferences"); }, }; } diff --git a/src/renderer/components/App.tsx b/src/renderer/components/App.tsx index b9e72f33..535922e9 100755 --- a/src/renderer/components/App.tsx +++ b/src/renderer/components/App.tsx @@ -4,6 +4,7 @@ import React, { Component, ReactNode } from "react"; import { toggleWindowSize } from "../electron/window"; import { translations } from "../utils/i18n"; +import GoToDateOverlayContainer from "./overlays/go-to-date-overlay/GoToDateOverlayContainer"; import ImportOverlayContainer from "./overlays/import-overlay/ImportOverlayContainer"; import PrefOverlayContainer from "./overlays/pref-overlay/PrefOverlayContainer"; import StatsOverlayContainer from "./overlays/stats-overlay/StatsOverlayContainer"; @@ -38,6 +39,8 @@ export default class App extends Component { switch (overlay) { case "none": return null; + case "go-to-date": + return ; case "import": return ; case "preferences": diff --git a/src/renderer/components/overlays/go-to-date-overlay/GoToDateOverlay.tsx b/src/renderer/components/overlays/go-to-date-overlay/GoToDateOverlay.tsx new file mode 100644 index 00000000..f973df9a --- /dev/null +++ b/src/renderer/components/overlays/go-to-date-overlay/GoToDateOverlay.tsx @@ -0,0 +1,69 @@ +import logger from "electron-log"; +import React, { ChangeEvent, FormEvent, ReactElement, useState } from "react"; + +import { momentIndex, toIndexDate } from "../../../utils/dateFormat"; +import { translations } from "../../../utils/i18n"; +import OverlayContainer from "../overlay-hoc/OverlayContainer"; + +export interface StateProps { + allowFutureEntries: boolean; + dateSelected: Date; +} + +export interface DispatchProps { + closeOverlay: () => void; + setDateSelected: (date: Date) => void; +} + +type Props = StateProps & DispatchProps; + +/** + * Dialog for quickly jumping to a certain date. + */ +export default function GoToDateOverlay(props: Props): ReactElement { + const { allowFutureEntries, closeOverlay, dateSelected, setDateSelected } = props; + + const todayFormatted = toIndexDate(new Date()); + const dateSelectedFormatted = toIndexDate(dateSelected); + + // `date` can become `undefined` when the user's date input is incomplete (e.g. year not filled in + // yet + const [date, setDate] = useState(dateSelectedFormatted); + + const onChange = (event: ChangeEvent): void => setDate(event.target.value); + + const onSubmit = (event: FormEvent): void => { + event.preventDefault(); + if (!date) { + logger.error("Cannot go to date: Date is not defined"); + } else { + setDateSelected(momentIndex(date).toDate()); + closeOverlay(); + } + }; + + const canSubmit = + date && // `date` must be defined (i.e. user must have provided a valid date input) + date !== dateSelectedFormatted && // Selected date cannot be the currently selected one + (allowFutureEntries || date <= todayFormatted); // Disallow future dates if option is set + + return ( + +
+

{translations["go-to-date"]}

+ + +
+
+ ); +} diff --git a/src/renderer/components/overlays/go-to-date-overlay/GoToDateOverlayContainer.tsx b/src/renderer/components/overlays/go-to-date-overlay/GoToDateOverlayContainer.tsx new file mode 100644 index 00000000..7f345780 --- /dev/null +++ b/src/renderer/components/overlays/go-to-date-overlay/GoToDateOverlayContainer.tsx @@ -0,0 +1,20 @@ +import { connect } from "react-redux"; + +import { closeOverlay } from "../../../store/app/actionCreators"; +import { SetOverlayAction } from "../../../store/app/types"; +import { setDateSelected } from "../../../store/diary/actionCreators"; +import { SetDateSelectedAction } from "../../../store/diary/types"; +import { RootState, ThunkDispatchT } from "../../../store/store"; +import GoToDateOverlay, { DispatchProps, StateProps } from "./GoToDateOverlay"; + +const mapStateToProps = (state: RootState): StateProps => ({ + allowFutureEntries: state.app.allowFutureEntries, + dateSelected: state.diary.dateSelected, +}); + +const mapDispatchToProps = (dispatch: ThunkDispatchT): DispatchProps => ({ + closeOverlay: (): SetOverlayAction => dispatch(closeOverlay()), + setDateSelected: (date: Date): SetDateSelectedAction => dispatch(setDateSelected(date)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(GoToDateOverlay); diff --git a/src/renderer/electron/ipcRenderer/listeners.ts b/src/renderer/electron/ipcRenderer/listeners.ts index 7af57ffe..767e9eac 100644 --- a/src/renderer/electron/ipcRenderer/listeners.ts +++ b/src/renderer/electron/ipcRenderer/listeners.ts @@ -88,12 +88,8 @@ function initIpcListeners(): void { // Overlays - ipcRenderer.on("showPrefOverlay", (): void => { - dispatchThunk(openOverlay("preferences")); - }); - - ipcRenderer.on("showStatsOverlay", (): void => { - dispatchThunk(openOverlay("statistics")); + ipcRenderer.on("openOverlay", (_, overlayType: OverlayType): void => { + dispatchThunk(openOverlay(overlayType)); }); // Screen lock diff --git a/src/renderer/types.ts b/src/renderer/types.ts index 47c32840..129fe2de 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -23,7 +23,7 @@ interface Metadata { // Overlay -type OverlayType = "none" | "import" | "preferences" | "statistics"; +type OverlayType = "none" | "go-to-date" | "import" | "preferences" | "statistics"; // Theme diff --git a/src/shared/types.ts b/src/shared/types.ts index 0afe20df..ac80bc9e 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -29,6 +29,7 @@ export interface Translations { // Menu (app-specific) "export": string, "export-to-format": string, + "go-to-date": string, "import": string, "import-from-format": string, "license": string, @@ -68,6 +69,7 @@ export interface Translations { "allow-future-entries": string, "diary-entries": string, "no": string, + "ok": string, "reset-diary": string, "reset-diary-confirm": string, "reset-diary-msg": string,