From ef6a4c01a6dbcc1b2f92207067ce96741ee34e72 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:38:07 -0500 Subject: [PATCH 1/7] Improve no access state --- plugins/notion/src/App.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/notion/src/App.css b/plugins/notion/src/App.css index 7eaa940dd..adb50ef49 100644 --- a/plugins/notion/src/App.css +++ b/plugins/notion/src/App.css @@ -37,6 +37,10 @@ form { gap: 10px; } +p a { + cursor: pointer; +} + .sticky-divider { position: sticky; top: 0; @@ -296,6 +300,10 @@ select:not(:disabled) { width: 100%; } +.actions a { + display: contents; +} + .action-button { flex: 1; width: 100%; From 9931c23663aad30fc089552b0bae2e11611ea7a1 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:01:54 -0500 Subject: [PATCH 2/7] Add progress bar modal --- plugins/notion/src/App.css | 28 ++++++++++++++++++++++++++++ plugins/notion/src/App.tsx | 8 ++++++-- plugins/notion/src/FieldMapping.tsx | 15 +++++++++------ plugins/notion/src/Progress.tsx | 19 +++++++++++++++++++ plugins/notion/src/ui.ts | 10 ++++++++++ 5 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 plugins/notion/src/Progress.tsx diff --git a/plugins/notion/src/App.css b/plugins/notion/src/App.css index adb50ef49..4766d1cfc 100644 --- a/plugins/notion/src/App.css +++ b/plugins/notion/src/App.css @@ -308,3 +308,31 @@ select:not(:disabled) { flex: 1; width: 100%; } + +/* Progress State */ + +.progress-bar-text { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.progress-bar { + height: 3px; + width: 100%; + flex-shrink: 0; + border-radius: 10px; + background-color: var(--framer-color-bg-tertiary); + position: relative; +} + +.progress-bar-fill { + position: absolute; + top: 0; + bottom: 0; + left: 0; + border-radius: 10px; + background-color: var(--framer-color-tint); +} diff --git a/plugins/notion/src/App.tsx b/plugins/notion/src/App.tsx index 9ed6dd418..72023ba7d 100644 --- a/plugins/notion/src/App.tsx +++ b/plugins/notion/src/App.tsx @@ -8,7 +8,7 @@ import { type DatabaseIdMap, type DataSource, getDataSource } from "./data" import { FieldMapping } from "./FieldMapping" import { NoTableAccess } from "./NoAccess" import { SelectDataSource } from "./SelectDataSource" -import { showAccessErrorUI, showFieldMappingUI, showLoginUI } from "./ui" +import { showAccessErrorUI, showFieldMappingUI, showLoginUI, showProgressUI } from "./ui" interface AppProps { collection: ManagedCollection @@ -32,6 +32,7 @@ export function App({ const [dataSource, setDataSource] = useState(null) const [isLoadingDataSource, setIsLoadingDataSource] = useState(Boolean(previousDatabaseId)) const [hasAccessError, setHasAccessError] = useState(false) + const [isSyncing, setIsSyncing] = useState(false) // Support self-referencing databases by allowing the current collection to be referenced. const databaseIdMap = useMemo(() => { @@ -46,6 +47,8 @@ export function App({ try { if (hasAccessError) { await showAccessErrorUI() + } else if (isSyncing) { + await showProgressUI() } else if (dataSource || isLoadingDataSource) { await showFieldMappingUI() } else { @@ -60,7 +63,7 @@ export function App({ } void showUI() - }, [dataSource, isLoadingDataSource, hasAccessError]) + }, [dataSource, isLoadingDataSource, hasAccessError, isSyncing]) useEffect(() => { if (!previousDatabaseId) { @@ -149,6 +152,7 @@ export function App({ previousLastSynced={previousLastSynced} previousIgnoredFieldIds={previousIgnoredFieldIds} databaseIdMap={databaseIdMap} + setIsSyncing={setIsSyncing} /> ) } diff --git a/plugins/notion/src/FieldMapping.tsx b/plugins/notion/src/FieldMapping.tsx index 4b38baf37..94bc3e349 100644 --- a/plugins/notion/src/FieldMapping.tsx +++ b/plugins/notion/src/FieldMapping.tsx @@ -23,6 +23,7 @@ import { type SyncProgress, syncCollection, } from "./data" +import { Progress } from "./Progress" import { assert, syncMethods } from "./utils" const labelByFieldTypeOption: Record = { @@ -144,6 +145,7 @@ interface FieldMappingProps { previousLastSynced: string | null previousIgnoredFieldIds: string | null databaseIdMap: DatabaseIdMap + setIsSyncing: (isSyncing: boolean) => void } export function FieldMapping({ @@ -153,6 +155,7 @@ export function FieldMapping({ previousLastSynced, previousIgnoredFieldIds, databaseIdMap, + setIsSyncing, }: FieldMappingProps) { const isAllowedToManage = useIsAllowedTo("ManagedCollection.setFields", ...syncMethods) @@ -250,6 +253,7 @@ export function FieldMapping({ const task = async () => { try { setStatus("syncing-collection") + setIsSyncing(true) setSyncProgress(null) const fields = fieldsInfoToCollectionFields(fieldsInfo, databaseIdMap) @@ -284,6 +288,7 @@ export function FieldMapping({ ) } finally { setStatus("mapping-fields") + setIsSyncing(false) setSyncProgress(null) } } @@ -299,7 +304,9 @@ export function FieldMapping({ ) } - const progressPercent = syncProgress ? ((syncProgress.current / syncProgress.total) * 100).toFixed(1) : null + if (isSyncing) { + return + } return (
@@ -354,11 +361,7 @@ export function FieldMapping({ tabIndex={0} title={!isAllowedToManage ? "Insufficient permissions" : undefined} > - {isSyncing ? ( - <>{!syncProgress ?
: {progressPercent}%} - ) : ( - Import from {dataSourceName.trim() ? dataSourceName : "Untitled"} - )} + Import from {dataSourceName.trim() ? dataSourceName : "Untitled"} diff --git a/plugins/notion/src/Progress.tsx b/plugins/notion/src/Progress.tsx new file mode 100644 index 000000000..e71a2d5a4 --- /dev/null +++ b/plugins/notion/src/Progress.tsx @@ -0,0 +1,19 @@ +export function Progress({ current, total }: { current: number; total: number }) { + const progressPercent = total > 0 ? ((current / total) * 100).toFixed(1).replace(".0", "") : "0" + + return ( +
+
+

{progressPercent}%

+

+ {current} / {total} +

+
+
+
+
+

Syncing... please keep the plugin open until the process is complete.

+ +
+ ) +} diff --git a/plugins/notion/src/ui.ts b/plugins/notion/src/ui.ts index 53cdd3aa0..ae3f181c6 100644 --- a/plugins/notion/src/ui.ts +++ b/plugins/notion/src/ui.ts @@ -27,3 +27,13 @@ export async function showLoginUI() { resizable: false, }) } + +export async function showProgressUI() { + await framer.showUI({ + width: 260, + height: 147, + minWidth: 260, + minHeight: 147, + resizable: false, + }) +} From 959a788d9d19d78aaafaccaa20e35f5dc0bd7cb0 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:11:08 -0500 Subject: [PATCH 3/7] Report item count when loaded, remove cancel button --- plugins/notion/src/Progress.tsx | 1 - plugins/notion/src/data.ts | 2 ++ plugins/notion/src/ui.ts | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/notion/src/Progress.tsx b/plugins/notion/src/Progress.tsx index e71a2d5a4..3e9cbba5f 100644 --- a/plugins/notion/src/Progress.tsx +++ b/plugins/notion/src/Progress.tsx @@ -13,7 +13,6 @@ export function Progress({ current, total }: { current: number; total: number })

Syncing... please keep the plugin open until the process is complete.

-
) } diff --git a/plugins/notion/src/data.ts b/plugins/notion/src/data.ts index fbb782929..b86fa1f91 100644 --- a/plugins/notion/src/data.ts +++ b/plugins/notion/src/data.ts @@ -134,6 +134,8 @@ export async function syncCollection( let processedCount = 0 const totalItems = databaseItems.length + onProgress?.({ current: processedCount, total: totalItems }) + const promises = databaseItems.map((item, index) => limit(async () => { seenItemIds.add(item.id) diff --git a/plugins/notion/src/ui.ts b/plugins/notion/src/ui.ts index ae3f181c6..7f75524c9 100644 --- a/plugins/notion/src/ui.ts +++ b/plugins/notion/src/ui.ts @@ -31,9 +31,9 @@ export async function showLoginUI() { export async function showProgressUI() { await framer.showUI({ width: 260, - height: 147, + height: 102, minWidth: 260, - minHeight: 147, + minHeight: 102, resizable: false, }) } From 58f7a93d53f8be4ed7198ca4162624849383e643 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:41:15 -0500 Subject: [PATCH 4/7] Load item count progress, format numbers --- plugins/notion/src/App.css | 2 ++ plugins/notion/src/Progress.tsx | 7 +++++-- plugins/notion/src/api.ts | 21 +++++++++++++++++---- plugins/notion/src/data.ts | 4 +--- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/plugins/notion/src/App.css b/plugins/notion/src/App.css index 4766d1cfc..b4d62796d 100644 --- a/plugins/notion/src/App.css +++ b/plugins/notion/src/App.css @@ -253,6 +253,8 @@ select:not(:disabled) { height: 100%; padding: 0px 15px 15px 15px; gap: 15px; + user-select: none; + -webkit-user-select: none; } .login-image { diff --git a/plugins/notion/src/Progress.tsx b/plugins/notion/src/Progress.tsx index 3e9cbba5f..408690262 100644 --- a/plugins/notion/src/Progress.tsx +++ b/plugins/notion/src/Progress.tsx @@ -1,18 +1,21 @@ export function Progress({ current, total }: { current: number; total: number }) { const progressPercent = total > 0 ? ((current / total) * 100).toFixed(1).replace(".0", "") : "0" + const formatter = new Intl.NumberFormat('en-US') + const formattedCurrent = formatter.format(current) + const formattedTotal = formatter.format(total) return (

{progressPercent}%

- {current} / {total} + {formattedCurrent} / {formattedTotal}

-

Syncing... please keep the plugin open until the process is complete.

+

{current > 0 ? "Syncing" : "Loading data"}… please keep the plugin open until the process is complete.

) } diff --git a/plugins/notion/src/api.ts b/plugins/notion/src/api.ts index f3c8d3062..195567517 100644 --- a/plugins/notion/src/api.ts +++ b/plugins/notion/src/api.ts @@ -394,15 +394,28 @@ export async function getPageBlocksAsRichText(pageId: string) { return blocksToHtml(blocks) } -export async function getDatabaseItems(database: GetDatabaseResponse): Promise { +export async function getDatabaseItems( + database: GetDatabaseResponse, + onProgress?: (progress: { current: number; total: number }) => void +): Promise { const notion = getNotionClient() - const data = await collectPaginatedAPI(notion.databases.query, { + const data: PageObjectResponse[] = [] + let itemCount = 0 + + const databaseIterator = iteratePaginatedAPI(notion.databases.query, { database_id: database.id, }) - assert(data.every(isFullPage), "Response is not a full page") - return data + for await (const item of databaseIterator) { + data.push(item as PageObjectResponse) + itemCount++ + onProgress?.({ current: 0, total: itemCount }) + } + + const pages = data.filter(isFullPage) + + return pages } export function isUnchangedSinceLastSync(lastEditedTime: string, lastSyncedTime: string | null): boolean { diff --git a/plugins/notion/src/data.ts b/plugins/notion/src/data.ts index b86fa1f91..15697a6f6 100644 --- a/plugins/notion/src/data.ts +++ b/plugins/notion/src/data.ts @@ -127,15 +127,13 @@ export async function syncCollection( const seenItemIds = new Set() - const databaseItems = await getDatabaseItems(dataSource.database) + const databaseItems = await getDatabaseItems(dataSource.database, onProgress) const limit = pLimit(CONCURRENCY_LIMIT) // Progress tracking let processedCount = 0 const totalItems = databaseItems.length - onProgress?.({ current: processedCount, total: totalItems }) - const promises = databaseItems.map((item, index) => limit(async () => { seenItemIds.add(item.id) From ce47399e99d8fbe5810823f41c8a893df74a670a Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:49:26 -0500 Subject: [PATCH 5/7] Eslint fix --- plugins/notion/src/FieldMapping.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/notion/src/FieldMapping.tsx b/plugins/notion/src/FieldMapping.tsx index 94bc3e349..73ceb6f4d 100644 --- a/plugins/notion/src/FieldMapping.tsx +++ b/plugins/notion/src/FieldMapping.tsx @@ -357,7 +357,7 @@ export function FieldMapping({