Skip to content

Commit 9ff5505

Browse files
feat: notification toast now contains link to publication in library (PR #2884)
Co-authored-by: Daniel Weck <daniel.weck@gmail.com>
1 parent cfd8f71 commit 9ff5505

File tree

40 files changed

+169
-23
lines changed

40 files changed

+169
-23
lines changed

src/common/redux/actions/toast/openRequest.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,22 @@ export const ID = "TOAST_OPEN_REQUEST";
1313
export interface Payload {
1414
type: ToastType;
1515
data: string | undefined;
16+
publicationTitle: string;
1617
publicationIdentifier: string | undefined;
18+
publicationHash: string | undefined;
1719
}
1820

19-
export function build(type: ToastType, data: string, publicationIdentifier?: string):
21+
export function build(type: ToastType, data: string, publicationTitle?: string, publicationIdentifier?: string, publicationHash?: string):
2022
Action<typeof ID, Payload> {
2123

2224
return {
2325
type: ID,
2426
payload: {
2527
type,
2628
data,
29+
publicationTitle,
2730
publicationIdentifier,
31+
publicationHash,
2832
},
2933
};
3034
}

src/common/redux/reducers/toast.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const initialState: ToastState = {
2020
type: ToastType.Success,
2121
data: undefined,
2222
publicationIdentifier: undefined,
23+
publicationTitle: undefined,
24+
publicationHash: undefined,
2325
};
2426

2527
function toastReducer_(
@@ -36,6 +38,8 @@ function toastReducer_(
3638
type: action.payload.type,
3739
data: action.payload.data,
3840
publicationIdentifier: action.payload.publicationIdentifier,
41+
publicationTitle: action.payload.publicationTitle,
42+
publicationHash: action.payload.publicationHash,
3943
},
4044
);
4145
case toastActions.closeRequest.ID:

src/common/redux/states/toast.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ export interface ToastState {
1212
type: ToastType;
1313
data: string;
1414
publicationIdentifier: string | undefined;
15+
publicationTitle: string | undefined;
16+
publicationHash: string | undefined;
1517
}

src/common/views/publication.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export interface PublicationView extends Identifiable {
5151
publicationTitle: string | IStringMap; // convertMultiLangStringToLangString()
5252
publicationSubTitle: string | IStringMap; // convertMultiLangStringToLangString()
5353

54+
publicationHash: string;
55+
5456
authorsLangString: (string | IStringMap)[]; // convertMultiLangStringToLangString()
5557
publishersLangString?: (string | IStringMap)[]; // convertMultiLangStringToLangString()
5658

src/main/converter/publication.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ export class PublicationViewConverter {
296296
// convertMultiLangStringToLangString()
297297
publicationSubTitle: r2Publication.Metadata.SubTitle, // string | IStringMap
298298

299+
publicationHash: document.hash,
300+
299301
// convertMultiLangStringToLangString()
300302
publishersLangString,
301303
// convertMultiLangStringToLangString()

src/main/redux/sagas/api/publication/import/index.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ import { diMainGet } from "readium-desktop/main/di";
1414
// eslint-disable-next-line local-rules/typed-redux-saga-use-typed-effects
1515
import { put } from "redux-saga/effects";
1616
import { SagaGenerator } from "typed-redux-saga";
17-
import { all as allTyped, call as callTyped } from "typed-redux-saga/macro";
18-
17+
import { all as allTyped, call as callTyped, select as selectTyped } from "typed-redux-saga/macro";
1918
import { importFromFsService } from "./importFromFs";
2019
import { importFromLinkService } from "./importFromLink";
2120
import { importFromStringService } from "./importFromString";
2221
import { PublicationDocument } from "readium-desktop/main/db/document/publication";
2322
import { PublicationViewConverter } from "readium-desktop/main/converter/publication";
2423
import { getTranslator } from "readium-desktop/common/services/translator";
24+
import { RootState } from "readium-desktop/main/redux/states";
25+
import { convertMultiLangStringToString } from "readium-desktop/common/language-string";
2526

2627
// Logger
2728
const debug = debug_("readium-desktop:main#saga/api/publication/import");
@@ -38,7 +39,6 @@ export function* importFromLink(
3839
const translate = getTranslator().translate;
3940

4041
try {
41-
4242
const [publicationDocument, alreadyImported] = yield* callTyped(importFromLinkService, link, pub);
4343

4444
if (!publicationDocument) {
@@ -48,24 +48,32 @@ export function* importFromLink(
4848
const publicationViewConverter = diMainGet("publication-view-converter");
4949
const publicationView = yield* callTyped(() => convertDoc(publicationDocument, publicationViewConverter));
5050

51+
const locale = yield* selectTyped((state: RootState) => state.i18n.locale);
52+
// convertMultiLangStringToLangString
53+
const pubTitleLangStr = convertMultiLangStringToString(publicationView.publicationTitle || publicationView.documentTitle || "", locale);
54+
5155
if (alreadyImported) {
5256
yield put(
5357
toastActions.openRequest.build(
5458
ToastType.Success,
5559
translate("message.import.alreadyImport",
56-
{ title: publicationView.documentTitle }),
60+
{ title: pubTitleLangStr }),
61+
pubTitleLangStr,
62+
undefined,
63+
publicationDocument.hash,
5764
),
5865
);
59-
6066
} else {
6167
yield put(
6268
toastActions.openRequest.build(
6369
ToastType.Success,
6470
translate("message.import.success",
65-
{ title: publicationView.documentTitle }),
71+
{ title: pubTitleLangStr }),
72+
pubTitleLangStr,
73+
undefined,
74+
publicationDocument.hash,
6675
),
6776
);
68-
6977
}
7078

7179
return publicationView;
@@ -143,24 +151,32 @@ export function* importFromFs(
143151

144152
const publicationView = yield* callTyped(() => convertDoc(publicationDocument, publicationViewConverter));
145153

154+
const locale = yield* selectTyped((state: RootState) => state.i18n.locale);
155+
// convertMultiLangStringToLangString
156+
const pubTitleLangStr = convertMultiLangStringToString(publicationView.publicationTitle || publicationView.documentTitle || "", locale);
157+
146158
if (alreadyImported) {
147159
yield put(
148160
toastActions.openRequest.build(
149161
ToastType.Success,
150162
translate("message.import.alreadyImport",
151-
{ title: publicationView.documentTitle }),
163+
{ title: pubTitleLangStr }),
164+
pubTitleLangStr,
165+
undefined,
166+
publicationDocument.hash,
152167
),
153168
);
154-
155169
} else {
156170
yield put(
157171
toastActions.openRequest.build(
158172
ToastType.Success,
159173
translate("message.import.success",
160-
{ title: publicationView.documentTitle }),
174+
{ title: pubTitleLangStr }),
175+
pubTitleLangStr,
176+
undefined,
177+
publicationDocument.hash,
161178
),
162179
);
163-
164180
}
165181

166182
return publicationView;

src/renderer/common/components/toast/Toast.tsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import * as ChevronDownIcon from "readium-desktop/renderer/assets/icons/chevron-
1818
import { TranslatorProps, withTranslator } from "../hoc/translator";
1919
import { connect } from "react-redux";
2020
import { IRendererCommonRootState } from "readium-desktop/common/redux/states/rendererCommonRootState";
21+
import { Link } from "react-router-dom";
2122

23+
// import { PublicationView } from "readium-desktop/common/views/publication";
2224

2325
const capitalizedAppName = _APP_NAME.charAt(0).toUpperCase() + _APP_NAME.substring(1);
2426

@@ -31,6 +33,9 @@ interface IBaseProps extends TranslatorProps {
3133
message?: string;
3234
displaySystemNotification?: boolean;
3335
type?: ToastType;
36+
publicationTitle?: string;
37+
publicationHash?: string;
38+
// openReader? : (publicationView: PublicationView) => void;
3439
}
3540

3641
// IProps may typically extend:
@@ -85,7 +90,7 @@ export class Toast extends React.Component<IProps, IState> {
8590
this.timer = window.setTimeout(() => {
8691
this.timer = undefined;
8792
this.handleClose();
88-
}, fast ? 500 : 5000);
93+
}, fast ? 1000 : 6000);
8994
}
9095

9196
public componentDidMount() {
@@ -183,7 +188,46 @@ export class Toast extends React.Component<IProps, IState> {
183188
// ignore
184189
}
185190
}
186-
}>{ this.props.message }</p>
191+
}>{this.props.publicationTitle || this.props.publicationHash ?
192+
<span>
193+
{this.props.message}
194+
<br/>
195+
<Link
196+
style={{textDecoration: "underline", fontStyle:"italic", fontWeight: "bold", color: "var(--color-primary)"}}
197+
to={{
198+
...location,
199+
pathname: "/library",
200+
search: `?focus=search${this.props.publicationHash ? "&searchPubHash=" + encodeURIComponent(this.props.publicationHash) : ""}${this.props.publicationTitle ? "&searchPubTitle=" + encodeURIComponent(this.props.publicationTitle) : ""}`,
201+
}}
202+
onClick={(e) => {
203+
if (e.altKey || e.shiftKey || e.ctrlKey) {
204+
e.preventDefault();
205+
e.currentTarget.click();
206+
}
207+
}}
208+
onKeyDown={(e) => {
209+
// if (e.code === "Space") {
210+
if (e.key === " " || e.altKey || e.ctrlKey) {
211+
e.preventDefault(); // prevent scroll
212+
}
213+
}}
214+
onKeyUp={(e) => {
215+
// Includes screen reader tests:
216+
// if (e.code === "Space") { WORKS
217+
// if (e.key === "Space") { DOES NOT WORK
218+
// if (e.key === "Enter") { WORKS
219+
if (e.key === " ") { // WORKS
220+
e.preventDefault();
221+
e.currentTarget.click();
222+
}
223+
}}
224+
>
225+
{__("message.import.seeInLibrary")}
226+
</Link>
227+
</span>
228+
:
229+
(this.props.message)
230+
}</p>
187231
{/*
188232
onBlur={() => {
189233
this.triggerTimer(true);

src/renderer/common/components/toast/ToastManager.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ export class ToastManager extends React.Component<IProps, IState> {
7171
close={ () => this.close(id) }
7272
type={toast.type}
7373
displaySystemNotification={false}
74+
publicationTitle={toast.publicationTitle}
75+
publicationHash={toast.publicationHash}
7476
/>;
7577
case ToastType.Default:
7678
return <Toast

src/renderer/library/components/searchResult/AllPublicationPage.tsx

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ import classNames from "classnames";
8888
import * as Popover from "@radix-ui/react-popover";
8989

9090
// import { PublicationInfoLibWithRadix, PublicationInfoLibWithRadixContent, PublicationInfoLibWithRadixTrigger } from "../dialog/publicationInfos/PublicationInfo";
91-
import { useSearchParams } from "react-router-dom";
91+
import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
9292
// import * as FilterIcon from "readium-desktop/renderer/assets/icons/filter-icon.svg";
9393
// import * as DeleteFilter from "readium-desktop/renderer/assets/icons/deleteFilter-icon.svg";
9494
import { MySelectProps, Select } from "readium-desktop/renderer/common/components/Select";
@@ -397,6 +397,20 @@ const CellGlobalFilter: React.FC<ITableCellProps_GlobalFilter> = (props) => {
397397
// className={classNames(classThemeExample)}
398398
// className={classNames(classStyleExample)}
399399

400+
const navigate = useNavigate();
401+
const location = useLocation();
402+
const queryParams = new URLSearchParams(location.search);
403+
const searchPubTitle = queryParams.get("searchPubTitle") || undefined;
404+
const searchPubHash = queryParams.get("searchPubHash") || undefined;
405+
406+
React.useEffect(() => {
407+
const val = searchPubHash ? `id:${searchPubHash}` : (searchPubTitle || "");
408+
if (val && props.focusInputRef?.current && props.focusInputRef.current.value !== val) {
409+
props.focusInputRef.current.value = val;
410+
onInputChange(val);
411+
}
412+
}, [searchPubHash, searchPubTitle, onInputChange, props.focusInputRef, props.focusInputRef?.current?.value]);
413+
400414
return (
401415
<div className={classNames(stylesInput.form_group, stylesInput.form_group_allPubSearch)}>
402416
<label
@@ -422,24 +436,35 @@ const CellGlobalFilter: React.FC<ITableCellProps_GlobalFilter> = (props) => {
422436

423437
onChange={(e) => {
424438
// setValue(e.target.value);
439+
const val = (e.target.value || "").trim();
440+
if (queryParams.has("searchPubTitle") || queryParams.has("searchPubHash")) {
441+
// navigate(location.pathname + (val ? `?focus=search&searchPubTitle=${encodeURIComponent(val)}` : ""), {
442+
// state: location.state,
443+
// replace: true,
444+
// });
445+
navigate(location.pathname, {
446+
state: location.state,
447+
replace: true,
448+
});
449+
}
425450
if (!props.accessibilitySupportEnabled) {
426-
onInputChange((e.target.value || "").trim() || undefined);
451+
onInputChange(val);
427452
}
428453
}}
429454
onKeyUp={(e) => {
455+
const val = (props.focusInputRef?.current?.value || "").trim();
430456
if (props.accessibilitySupportEnabled && e.key === "Enter") {
431457
props.setShowColumnFilters(true);
432-
props.setGlobalFilter( // value
433-
(props.focusInputRef?.current?.value || "").trim() || undefined);
458+
props.setGlobalFilter(val);
434459
}
435460
}}
436461
placeholder={`${props.__("header.searchTitle")}`}
437462
/>
438463
{props.accessibilitySupportEnabled ? <button
439464
onClick={() => {
465+
const val = (props.focusInputRef?.current?.value || "").trim();
440466
props.setShowColumnFilters(true);
441-
props.setGlobalFilter( // value
442-
(props.focusInputRef?.current?.value || "").trim() || undefined);
467+
props.setGlobalFilter(val);
443468
}}
444469
>{`${props.__("header.searchPlaceholder")}`}</button> : <></>}
445470
</div>
@@ -1362,6 +1387,8 @@ interface IColumns {
13621387
// colIdentifier: string;
13631388
// colPublicationType: string;
13641389
// colProgression: string;
1390+
1391+
col_pubHash: string;
13651392
}
13661393

13671394
// https://gist.github.com/ggascoigne/646e14c9d54258e40588a13aabf0102d
@@ -1657,6 +1684,8 @@ export const TableView: React.FC<ITableCellProps_TableView & ITableCellProps_Com
16571684
// colProgression: "Progression",
16581685
// colIdentifier: identifier,
16591686
// colPublicationType: publicationType,
1687+
1688+
col_pubHash: "id:" + publicationView.publicationHash,
16601689
};
16611690
return cols;
16621691
});
@@ -1905,6 +1934,12 @@ export const TableView: React.FC<ITableCellProps_TableView & ITableCellProps_Com
19051934
// accessor: "colPublicationType",
19061935
// sortType: sortFunction,
19071936
// },
1937+
1938+
{
1939+
Header: "Publication Hash",
1940+
accessor: "col_pubHash",
1941+
sortType: sortFunction,
1942+
},
19081943
];
19091944
return arr;
19101945
}, [__]);
@@ -1970,7 +2005,7 @@ export const TableView: React.FC<ITableCellProps_TableView & ITableCellProps_Com
19702005
const initialState: UsePaginationState<IColumns> & TableState<IColumns> = {
19712006
pageSize: PAGESIZE, // displayType === DisplayType.List ? 20 : 10;
19722007
pageIndex: 0,
1973-
hiddenColumns: displayType === DisplayType.Grid ? ["colLanguages", "colPublishers", "colPublishedDate", "colLCP", "colDuration", "colDescription", "col_a11y_accessibilitySummary"] : [],
2008+
hiddenColumns: displayType === DisplayType.Grid ? ["colLanguages", "colPublishers", "colPublishedDate", "colLCP", "colDuration", "colDescription", "col_a11y_accessibilitySummary", "col_pubHash"] : ["col_pubHash"],
19742009
};
19752010
const opts:
19762011
TableOptions<IColumns> &

src/renderer/library/redux/sagas/sameFileImport.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,16 @@ function* sameFileImport(action: importActions.verify.TAction) {
3636
return tuple[0].downloadUrls;
3737
}).find((urls) => urls.find((u) => u === link.url))
3838
) {
39+
// const locale = yield* selectTyped((state: ILibraryRootState) => state.i18n.locale);
40+
// // convertMultiLangStringToLangString
41+
// const pubTitleLangStr = convertMultiLangStringToString(pub.publicationTitle || pub.documentTitle, locale);
42+
3943
yield put(
4044
toastActions.openRequest.build(
4145
ToastType.Success,
4246
getTranslator().__("message.import.alreadyImport",
4347
{
44-
title: pub.documentTitle || "",
48+
title: pub.documentTitle || "", // pubTitleLangStr
4549
},
4650
),
4751
),

0 commit comments

Comments
 (0)