From 191fcb2675e3b54824fd9e83df73521af03997d6 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 8 Mar 2022 13:54:20 -0800 Subject: [PATCH 1/6] send reading progres updates from embedded web views --- .../App/Views/LinkItemDetailView.swift | 2 + .../Views/Article/WebAppWrapperView.swift | 7 +++ .../Sources/Views/Article/WebView.swift | 1 + packages/web/additional.d.ts | 2 +- .../components/templates/article/Article.tsx | 49 ++++++++++++------- .../templates/article/HighlightsLayer.tsx | 20 ++++++-- 6 files changed, 58 insertions(+), 23 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index 3f0d9cead..aed43d232 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -91,6 +91,8 @@ final class LinkItemDetailViewModel: ObservableObject { switch action { case let .shareHighlight(highlightID): print("show share modal for highlight with id: \(highlightID)") + case let .updateReadingProgess(progress: progress): + print("new reading progress: \(progress)") } } .store(in: &newWebAppWrapperViewModel.subscriptions) diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift index 6f704e6b1..4fe383aca 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift @@ -6,6 +6,7 @@ import WebKit public final class WebAppWrapperViewModel: ObservableObject { public enum Action { case shareHighlight(highlightID: String) + case updateReadingProgess(progress: Double) } public var subscriptions = Set() @@ -86,6 +87,12 @@ public struct WebAppWrapperView: View { if message.name == WebViewAction.highlightAction.rawValue { handleHighlightAction(message: message) } + + if message.name == WebViewAction.readingProgressUpdate.rawValue { + guard let messageBody = message.body as? [String: String] else { return } + guard let progress = messageBody["progress"] else { return } + print(progress) + } } private func handleHighlightAction(message: WKScriptMessage) { diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebView.swift b/apple/OmnivoreKit/Sources/Views/Article/WebView.swift index dd802b68e..44931a57d 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/WebView.swift @@ -4,6 +4,7 @@ import WebKit /// The names on the javascript side must match for an action to be handled. enum WebViewAction: String, CaseIterable { case highlightAction + case readingProgressUpdate } final class WebView: WKWebView { diff --git a/packages/web/additional.d.ts b/packages/web/additional.d.ts index 552a77414..c121550ba 100644 --- a/packages/web/additional.d.ts +++ b/packages/web/additional.d.ts @@ -1,7 +1,6 @@ export {} declare global { - // eslint-disable-next-line functional/prefer-type-literal interface Window { webkit?: Webkit MathJax?: MathJax @@ -24,6 +23,7 @@ declare type Webkit = { declare type MessageHandlers = { viewerAction?: WebKitMessageHandler highlightAction?: WebKitMessageHandler + readingProgressUpdate?: WebKitMessageHandler } declare type WebKitMessageHandler = { diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx index e4b44f47d..f5d3cd00b 100644 --- a/packages/web/components/templates/article/Article.tsx +++ b/packages/web/components/templates/article/Article.tsx @@ -36,27 +36,37 @@ export function Article(props: ArticleProps): JSX.Element { props.initialAnchorIndex ) - const [ - shouldScrollToInitialPosition, - setShouldScrollToInitialPosition, - ] = useState(true) + const [shouldScrollToInitialPosition, setShouldScrollToInitialPosition] = + useState(true) const articleContentRef = useRef(null) useReadingProgressAnchor(articleContentRef, setReadingAnchorIndex) - const debouncedReadingProgress = useDebounce(readingProgress, 1000); - const debouncedReadingAnchorIndex = useDebounce(readingAnchorIndex, 1000); + const debouncedReadingProgress = useDebounce(readingProgress, 1000) + const debouncedReadingAnchorIndex = useDebounce(readingAnchorIndex, 1000) useEffect(() => { - (async () => { + ;(async () => { await articleReadingProgressMutation({ id: props.articleId, - readingProgressPercent: readingProgress, + readingProgressPercent: debouncedReadingProgress, readingProgressAnchorIndex: readingAnchorIndex, }) })() - }, [props.articleId, debouncedReadingProgress, debouncedReadingAnchorIndex]) + }, [ + props.articleId, + debouncedReadingProgress, + debouncedReadingAnchorIndex, + readingAnchorIndex, + ]) + + // Post message to webkit so apple app embeds get progress updates + useEffect(() => { + window?.webkit?.messageHandlers.readingProgressUpdate?.postMessage({ + progress: debouncedReadingProgress, + }) + }, [debouncedReadingProgress]) const setScrollWatchedElement = useScrollWatcher( (changeset: ScrollOffsetChangeset) => { @@ -182,16 +192,19 @@ export function Article(props: ArticleProps): JSX.Element { return ( <> - + ) diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx index 5f9938acc..714b5ae3e 100644 --- a/packages/web/components/templates/article/HighlightsLayer.tsx +++ b/packages/web/components/templates/article/HighlightsLayer.tsx @@ -6,7 +6,11 @@ import type { Highlight } from '../../../lib/networking/fragments/highlightFragm import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation' import { shareHighlightToFeedMutation } from '../../../lib/networking/mutations/shareHighlightToFeedMutation' import { shareHighlightCommentMutation } from '../../../lib/networking/mutations/updateShareHighlightCommentMutation' -import { highlightIdAttribute, highlightNoteIdAttribute, SelectionAttributes } from '../../../lib/highlights/highlightHelpers' +import { + highlightIdAttribute, + highlightNoteIdAttribute, + SelectionAttributes, +} from '../../../lib/highlights/highlightHelpers' import { HighlightBar, HighlightAction } from '../../patterns/HighlightBar' import { removeHighlights } from '../../../lib/highlights/deleteHighlight' import { createHighlight } from '../../../lib/highlights/createHighlight' @@ -49,7 +53,9 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { const [highlightModalAction, setHighlightModalAction] = useState({ highlightModalAction: 'none' }) - const [highlightLocations, setHighlightLocations] = useState([]) + const [highlightLocations, setHighlightLocations] = useState< + HighlightLocation[] + >([]) const focusedHighlightMousePos = useRef({ pageX: 0, pageY: 0 }) const [focusedHighlight, setFocusedHighlight] = useState< @@ -175,7 +181,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { [props.highlightBarDisabled] ) - const createHighlightFromSelection = async (selection: SelectionAttributes, note?: string): Promise => { + const createHighlightFromSelection = async ( + selection: SelectionAttributes, + note?: string + ): Promise => { const result = await createHighlight({ selection: selection, articleId: props.articleId, @@ -206,7 +215,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { if (!selectionData) { return } - const result = await createHighlightFromSelection(selectionData, annotation) + const result = await createHighlightFromSelection( + selectionData, + annotation + ) if (!result) { toast.error('Error saving highlight', { position: 'bottom-right' }) } From ddb13770d3d1a3056c5ffceea8d6d64e3ee87a15 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 8 Mar 2022 15:20:25 -0800 Subject: [PATCH 2/6] set position to fixed for article container wrapper in /app routes --- packages/web/components/templates/PrimaryLayout.tsx | 12 +++++++++--- .../web/components/templates/article/Article.tsx | 10 ++++++---- packages/web/pages/app/[username]/[slug]/index.tsx | 1 + 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/web/components/templates/PrimaryLayout.tsx b/packages/web/components/templates/PrimaryLayout.tsx index e1a6128d8..17757cacd 100644 --- a/packages/web/components/templates/PrimaryLayout.tsx +++ b/packages/web/components/templates/PrimaryLayout.tsx @@ -1,6 +1,12 @@ import { PageMetaData, PageMetaDataProps } from '../patterns/PageMetaData' import { Box } from '../elements/LayoutPrimitives' -import { ReactNode, MutableRefObject, useEffect, useContext, useState } from 'react' +import { + ReactNode, + MutableRefObject, + useEffect, + useContext, + useState, +} from 'react' import { PrimaryHeader } from './../patterns/PrimaryHeader' import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery' import { navigationCommands } from '../../lib/keyboardShortcuts/navigationShortcuts' @@ -11,7 +17,6 @@ import { ConfirmationModal } from '../patterns/ConfirmationModal' import { KeyboardShortcutListModal } from './KeyboardShortcutListModal' import { logoutMutation } from '../../lib/networking/mutations/logoutMutation' - type PrimaryLayoutProps = { children: ReactNode pageTestId: string @@ -25,7 +30,8 @@ export function PrimaryLayout(props: PrimaryLayoutProps): JSX.Element { const { viewerData } = useGetViewerQuery() const router = useRouter() const [showLogoutConfirmation, setShowLogoutConfirmation] = useState(false) - const [showKeyboardCommandsModal, setShowKeyboardCommandsModal] = useState(false) + const [showKeyboardCommandsModal, setShowKeyboardCommandsModal] = + useState(false) useKeyboardShortcuts(navigationCommands(router)) diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx index f5d3cd00b..eb88f59fc 100644 --- a/packages/web/components/templates/article/Article.tsx +++ b/packages/web/components/templates/article/Article.tsx @@ -63,10 +63,12 @@ export function Article(props: ArticleProps): JSX.Element { // Post message to webkit so apple app embeds get progress updates useEffect(() => { - window?.webkit?.messageHandlers.readingProgressUpdate?.postMessage({ - progress: debouncedReadingProgress, - }) - }, [debouncedReadingProgress]) + if (typeof window?.webkit != 'undefined') { + window.webkit.messageHandlers.readingProgressUpdate?.postMessage({ + progress: debouncedReadingProgress, + }) + } + }, [readingProgress, debouncedReadingProgress]) const setScrollWatchedElement = useScrollWatcher( (changeset: ScrollOffsetChangeset) => { diff --git a/packages/web/pages/app/[username]/[slug]/index.tsx b/packages/web/pages/app/[username]/[slug]/index.tsx index e08eb4c61..9d1693beb 100644 --- a/packages/web/pages/app/[username]/[slug]/index.tsx +++ b/packages/web/pages/app/[username]/[slug]/index.tsx @@ -69,6 +69,7 @@ function AppArticleEmbedContent( Date: Tue, 8 Mar 2022 20:53:26 -0800 Subject: [PATCH 3/6] update item reading progress when signal is ent from web app view --- .../Sources/App/Views/LinkItemDetailView.swift | 4 ++-- .../Sources/Views/Article/WebAppWrapperView.swift | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index aed43d232..78a3a4199 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -87,12 +87,12 @@ final class LinkItemDetailViewModel: ObservableObject { rawAuthCookie: rawAuthCookie ) - newWebAppWrapperViewModel.performActionSubject.sink { action in + newWebAppWrapperViewModel.performActionSubject.sink { [weak self] action in switch action { case let .shareHighlight(highlightID): print("show share modal for highlight with id: \(highlightID)") case let .updateReadingProgess(progress: progress): - print("new reading progress: \(progress)") + self?.item.readingProgress = Double(progress) } } .store(in: &newWebAppWrapperViewModel.subscriptions) diff --git a/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift b/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift index 4fe383aca..7c16c83a8 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/WebAppWrapperView.swift @@ -6,7 +6,7 @@ import WebKit public final class WebAppWrapperViewModel: ObservableObject { public enum Action { case shareHighlight(highlightID: String) - case updateReadingProgess(progress: Double) + case updateReadingProgess(progress: Int) } public var subscriptions = Set() @@ -87,11 +87,11 @@ public struct WebAppWrapperView: View { if message.name == WebViewAction.highlightAction.rawValue { handleHighlightAction(message: message) } - + if message.name == WebViewAction.readingProgressUpdate.rawValue { - guard let messageBody = message.body as? [String: String] else { return } + guard let messageBody = message.body as? [String: Double] else { return } guard let progress = messageBody["progress"] else { return } - print(progress) + viewModel.performActionSubject.send(.updateReadingProgess(progress: Int(progress))) } } From f7f21300f3b7cf7d96f9b67db64e35c1024886aa Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 9 Mar 2022 07:42:58 -0800 Subject: [PATCH 4/6] update reading progress on the item in home view model --- .../Sources/App/Views/Home/HomeFeedViewModel.swift | 7 +++++++ .../OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 1d7c11fed..011c1dba6 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -158,4 +158,11 @@ final class HomeFeedViewModel: ObservableObject { ) .store(in: &subscriptions) } + + func updateProgress(itemID: String, progress: Double) { + guard let item = items.first(where: { $0.id == itemID }) else { return } + if let index = items.firstIndex(of: item) { + items[index].readingProgress = progress + } + } } diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index 78a3a4199..36b19523f 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -92,7 +92,7 @@ final class LinkItemDetailViewModel: ObservableObject { case let .shareHighlight(highlightID): print("show share modal for highlight with id: \(highlightID)") case let .updateReadingProgess(progress: progress): - self?.item.readingProgress = Double(progress) + self?.homeFeedViewModel.updateProgress(itemID: self?.item.id ?? "", progress: Double(progress)) } } .store(in: &newWebAppWrapperViewModel.subscriptions) From b1b3915b7b245e67cf018abc52dd4ea020ff11ae Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 9 Mar 2022 08:25:54 -0800 Subject: [PATCH 5/6] send reading progress updates for ipad grid only --- .../Sources/App/Views/Home/HomeFeedViewIOS.swift | 6 ++++++ .../Sources/App/Views/Home/HomeFeedViewModel.swift | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index b74242259..04a07ba01 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -270,6 +270,9 @@ import Views } } .listStyle(PlainListStyle()) + .onAppear { + viewModel.sendProgressUpdates = false + } } } @@ -347,6 +350,9 @@ import Views LoadingSection() } } + .onAppear { + viewModel.sendProgressUpdates = true + } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 011c1dba6..e0ebabb8d 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -12,6 +12,7 @@ final class HomeFeedViewModel: ObservableObject { @Published var isLoading = false @Published var showPushNotificationPrimer = false var cursor: String? + var sendProgressUpdates = false // These are used to make sure we handle search result // responses in the right order @@ -160,7 +161,7 @@ final class HomeFeedViewModel: ObservableObject { } func updateProgress(itemID: String, progress: Double) { - guard let item = items.first(where: { $0.id == itemID }) else { return } + guard sendProgressUpdates, let item = items.first(where: { $0.id == itemID }) else { return } if let index = items.firstIndex(of: item) { items[index].readingProgress = progress } From bf035a4ec454a2419cbb3a2e8a5636ae067092ab Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 9 Mar 2022 09:48:30 -0800 Subject: [PATCH 6/6] use window to track scrolling for /app web routes --- packages/web/components/templates/article/Article.tsx | 6 ++++++ packages/web/pages/app/[username]/[slug]/index.tsx | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx index eb88f59fc..2f729a42b 100644 --- a/packages/web/components/templates/article/Article.tsx +++ b/packages/web/components/templates/article/Article.tsx @@ -79,6 +79,12 @@ export function Article(props: ArticleProps): JSX.Element { scrollContainer.scrollHeight setReadingProgress(newReadingProgress * 100) + } else if (window && window.document.scrollingElement) { + const newReadingProgress = + window.scrollY / window.document.scrollingElement.scrollHeight + const adjustedReadingProgress = + newReadingProgress > 0.92 ? 1 : newReadingProgress + setReadingProgress(adjustedReadingProgress * 100) } }, 1000 diff --git a/packages/web/pages/app/[username]/[slug]/index.tsx b/packages/web/pages/app/[username]/[slug]/index.tsx index 9d1693beb..7f871ab5f 100644 --- a/packages/web/pages/app/[username]/[slug]/index.tsx +++ b/packages/web/pages/app/[username]/[slug]/index.tsx @@ -67,9 +67,7 @@ function AppArticleEmbedContent( if (articleData) { return (