From 0dc7e0c4ad11f19396d28ba24786da0b44a98a55 Mon Sep 17 00:00:00 2001 From: Pavel Gnedov Date: Tue, 20 Jun 2023 02:57:54 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BF=D1=80=D0=B8=D0=B7=D0=BD=D0=B0=D0=BA=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D1=85=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../issues-list-board/issues-list-card.tsx | 6 +- frontend/src/kanban-board/kanban-card.tsx | 6 +- .../misc-components/issue-details-dialog.tsx | 10 ++- .../misc-components/unreaded-flag.module.css | 16 +++++ .../src/misc-components/unreaded-flag.tsx | 64 +++++++++++++++++++ frontend/src/utils/unreaded-provider.ts | 14 ++++ 6 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 frontend/src/misc-components/unreaded-flag.module.css create mode 100644 frontend/src/misc-components/unreaded-flag.tsx create mode 100644 frontend/src/utils/unreaded-provider.ts diff --git a/frontend/src/issues-list-board/issues-list-card.tsx b/frontend/src/issues-list-board/issues-list-card.tsx index d66e243..fd05210 100644 --- a/frontend/src/issues-list-board/issues-list-card.tsx +++ b/frontend/src/issues-list-board/issues-list-card.tsx @@ -6,20 +6,24 @@ import * as TimePassedNs from '../misc-components/time-passed'; import * as TagsNs from '../misc-components/tags'; import * as IssueHrefNs from '../misc-components/issue-href'; import * as IssueDetailsDialogNs from '../misc-components/issue-details-dialog'; +import * as UnreadedFlagNs from '../misc-components/unreaded-flag'; export type Props = { store: IIssueStore }; export const IssuesListCard = observer((props: Props): JSX.Element => { + const unreadedStore = UnreadedFlagNs.CreateStoreFromLocalStorage(props.store); const detailsStore = IssueDetailsDialogNs.Store.create({ issue: props.store, - visible: false + visible: false, + unreadedFlagStore: unreadedStore }); return (
{e.stopPropagation(); detailsStore.show();}}>
+ { keyName: 'timePassedClass' } } + const unreadedStore = UnreadedFlagNs.CreateStoreFromLocalStorage(props.store.issue); const detailsStore = IssueDetailsDialogNs.Store.create({ issue: props.store.issue, visible: false, - }) + unreadedFlagStore: unreadedStore + }); return (
{e.stopPropagation(); detailsStore.show();}}> diff --git a/frontend/src/misc-components/issue-details-dialog.tsx b/frontend/src/misc-components/issue-details-dialog.tsx index dc8982f..15c3fda 100644 --- a/frontend/src/misc-components/issue-details-dialog.tsx +++ b/frontend/src/misc-components/issue-details-dialog.tsx @@ -4,10 +4,13 @@ import { observer } from 'mobx-react-lite'; import { Instance, types } from 'mobx-state-tree'; import * as IssueHrefNs from '../misc-components/issue-href'; import Css from './issue-details-dialog.module.css'; +import * as UnreadedFlagNs from '../misc-components/unreaded-flag'; +import { SetIssueReadingTimestamp } from '../utils/unreaded-provider'; export const Store = types.model({ visible: types.boolean, - issue: types.frozen() + issue: types.frozen(), + unreadedFlagStore: types.maybe(UnreadedFlagNs.Store) }).actions((self) => { return { hide: () => { @@ -17,6 +20,11 @@ export const Store = types.model({ show: () => { console.debug(`Issue details dialog show: issue_id=${self.issue.id}`); // DEBUG self.visible = true; + if (self.unreadedFlagStore) { + self.unreadedFlagStore.read(); + } else { + SetIssueReadingTimestamp(self.issue.id); + } } }; }).views((self) => { diff --git a/frontend/src/misc-components/unreaded-flag.module.css b/frontend/src/misc-components/unreaded-flag.module.css new file mode 100644 index 0000000..6bda63f --- /dev/null +++ b/frontend/src/misc-components/unreaded-flag.module.css @@ -0,0 +1,16 @@ +.circle { + height: 6px; + width: 6px; + background-color: #707070; + border: 2px solid #707070; + border-radius: 50%; + display: inline-block; +} + +.circle.unreaded { + border: 2px solid #0000FF; +} + +.circle.forMe { + background-color: #FF0000; +} \ No newline at end of file diff --git a/frontend/src/misc-components/unreaded-flag.tsx b/frontend/src/misc-components/unreaded-flag.tsx new file mode 100644 index 0000000..f87da53 --- /dev/null +++ b/frontend/src/misc-components/unreaded-flag.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import Css from './unreaded-flag.module.css'; +import { observer } from 'mobx-react-lite'; +import { Instance, types } from 'mobx-state-tree'; +import { RedmineTypes } from '../redmine-types'; +import { GetIssueReadingTimestamp, SetIssueReadingTimestamp } from '../utils/unreaded-provider'; + +export const Store = types.model({ + issue: types.frozen(), + readingTimestamp: types.number +}).actions((self) => { + return { + read: () => { + self.readingTimestamp = SetIssueReadingTimestamp(self.issue.id); + } + }; +}).views((self) => { + return { + getUpdatedTimestap(): number { + if (self.issue.journals) { + let lastComment: RedmineTypes.Journal | undefined; + for (let i = self.issue.journals.length - 1; i >= 0; i--) { + const journal = self.issue.journals[i]; + if (journal.notes) { + lastComment = journal; + break; + } + } + if (lastComment) { + return (new Date(lastComment.created_on)).getTime(); + } + } + return 0; + }, + getClassName(): string { + let className = Css.circle; + const updatedTimestamp = this.getUpdatedTimestap(); + if (self.readingTimestamp < updatedTimestamp) { + className += ` ${Css.unreaded}`; + } + console.debug(`Unreaded flag getClassName: issueId=${self.issue.id}; readingTimestamp=${self.readingTimestamp}; updatedTimestamp=${updatedTimestamp}; className=${className}`); // DEBUG + return className; + } + }; +}); + +export function CreateStoreFromLocalStorage(issue: RedmineTypes.ExtendedIssue) { + const timestamp = GetIssueReadingTimestamp(issue.id); + return Store.create({ + issue: issue, + readingTimestamp: timestamp + }); +} + +export type Props = { + store: Instance +} + +export const UnreadedFlag = observer((props: Props): JSX.Element => { + const className = props.store.getClassName(); + return ( + {e.stopPropagation(); props.store.read();}}> + ); +}) \ No newline at end of file diff --git a/frontend/src/utils/unreaded-provider.ts b/frontend/src/utils/unreaded-provider.ts new file mode 100644 index 0000000..685f86c --- /dev/null +++ b/frontend/src/utils/unreaded-provider.ts @@ -0,0 +1,14 @@ +export function GetIssueReadingTimestamp(issueId: number): number { + const value = window.localStorage.getItem(getKey(issueId)); + return value ? Number(value) : 0; +} + +export function SetIssueReadingTimestamp(issueId: number): number { + const now = (new Date()).getTime(); + window.localStorage.setItem(getKey(issueId), String(now)); + return now; +} + +function getKey(issueId: number): string { + return `issue_read_${issueId}`; +} \ No newline at end of file