feat(core): track for integration (#11128)

This commit is contained in:
CatsJuice
2025-03-31 11:45:32 +00:00
parent f1882061a2
commit 731a4c952f
7 changed files with 175 additions and 16 deletions

View File

@@ -17,6 +17,7 @@ import {
getTokenLink,
inputErrorMsg,
} from './index.css';
import { readwiseTrack } from './track';
const ConnectDialog = ({
onClose,
@@ -48,6 +49,9 @@ const ConnectDialog = ({
const handleResult = useCallback(
(success: boolean, token: string) => {
readwiseTrack.connectIntegration({
result: success ? 'success' : 'failed',
});
if (success) {
onSuccess(token);
} else {

View File

@@ -7,6 +7,7 @@ import { useCallback, useState } from 'react';
import * as styles from './connected.css';
import { actionButton } from './index.css';
import { readwiseTrack } from './track';
export const DisconnectDialog = ({ onClose }: { onClose: () => void }) => {
const t = useI18n();
@@ -21,11 +22,13 @@ export const DisconnectDialog = ({ onClose }: { onClose: () => void }) => {
const handleCancel = useCallback(() => onClose(), [onClose]);
const handleKeep = useCallback(() => {
readwise.disconnect();
readwiseTrack.disconnectIntegration({ method: 'keep' });
onClose();
}, [onClose, readwise]);
const handleDelete = useAsyncCallback(async () => {
await readwise.deleteAll();
readwise.disconnect();
readwiseTrack.disconnectIntegration({ method: 'delete' });
onClose();
}, [onClose, readwise]);

View File

@@ -14,6 +14,7 @@ import { InformationFillDuotoneIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
import {
type ChangeEvent,
type Dispatch,
forwardRef,
type SetStateAction,
@@ -26,6 +27,7 @@ import {
import { Virtuoso } from 'react-virtuoso';
import * as styles from './import-dialog.css';
import { readwiseTrack } from './track';
export const ImportDialog = ({ onClose }: { onClose: () => void }) => {
const t = useI18n();
@@ -54,6 +56,13 @@ export const ImportDialog = ({ onClose }: { onClose: () => void }) => {
);
const handleConfirmImport = useCallback(
(ids: string[]) => {
readwiseTrack.confirmIntegrationImport({
control: 'Readwise import list',
method: readwise.setting$('lastImportedAt').value
? 'withtimestamp'
: 'new',
});
if (ids.length === 0) {
onClose();
return;
@@ -65,15 +74,26 @@ export const ImportDialog = ({ onClose }: { onClose: () => void }) => {
abortControllerRef.current = abortController;
const signal = abortController.signal;
const startTime = Date.now();
readwise
.highlightsToAffineDocs(selectedHighlights.reverse(), books, {
signal,
onProgress: setImportProgress,
onComplete: () => {
readwiseTrack.completeIntegrationImport({
total: selectedHighlights.length,
done: selectedHighlights.length,
time: (Date.now() - startTime) / 1000,
});
readwise.updateSetting('lastImportedAt', timestamp);
onClose();
},
onAbort: finished => {
readwiseTrack.abortIntegrationImport({
total: selectedHighlights.length,
done: finished,
time: (Date.now() - startTime) / 1000,
});
notify({
icon: <InformationFillDuotoneIcon />,
style: 'normal',
@@ -184,6 +204,10 @@ const SelectStage = ({
const [selected, setSelected] = useState<ReadwiseHighlight['id'][]>([]);
const handleResetLastImportedAt = useCallback(() => {
readwiseTrack.startIntegrationImport({
method: 'cleartimestamp',
control: 'Readwise import list',
});
readwise.updateSetting('lastImportedAt', undefined);
onResetLastImportedAt();
}, [onResetLastImportedAt, readwise]);
@@ -315,11 +339,17 @@ const HighlightTable = ({
.catch(console.error);
}, [readwise]);
const handleToggleSelectAll = useCallback(() => {
setSelected(prev =>
prev.length === highlights.length ? [] : highlights.map(h => h.id)
);
}, [highlights, setSelected]);
const handleToggleSelectAll = useCallback(
(_: ChangeEvent, checked: boolean) => {
readwiseTrack.selectIntegrationImport({
method: 'all',
option: checked ? 'on' : 'off',
control: 'Readwise import list',
});
setSelected(checked ? highlights.map(h => h.id) : []);
},
[highlights, setSelected]
);
return (
<div className={styles.table}>
@@ -358,14 +388,17 @@ const HighlightTable = ({
<div className={styles.tableCellSelect}>
<Checkbox
checked={selected.includes(highlight.id)}
onChange={() => {
setSelected(prev => {
if (prev.includes(highlight.id)) {
return prev.filter(id => id !== highlight.id);
} else {
return [...prev, highlight.id];
}
onChange={(_: ChangeEvent, checked: boolean) => {
readwiseTrack.selectIntegrationImport({
method: 'single',
option: checked ? 'on' : 'off',
control: 'Readwise import list',
});
setSelected(
checked
? [...selected, highlight.id]
: selected.filter(id => id !== highlight.id)
);
}}
/>
</div>

View File

@@ -16,6 +16,7 @@ import { ConnectButton } from './connect';
import { ConnectedActions } from './connected';
import { ImportDialog } from './import-dialog';
import { SettingDialog } from './setting-dialog';
import { readwiseTrack } from './track';
export const ReadwiseIntegration = () => {
const t = useI18n();
@@ -42,6 +43,14 @@ export const ReadwiseIntegration = () => {
setOpenImportDialog(true);
}, []);
const onImportClick = useCallback(() => {
readwiseTrack.startIntegrationImport({
method: settings?.lastImportedAt ? 'withtimestamp' : 'new',
control: 'Readwise Card',
});
handleImport();
}, [handleImport, settings?.lastImportedAt]);
return (
<IntegrationCard>
<IntegrationCardHeader
@@ -56,7 +65,7 @@ export const ReadwiseIntegration = () => {
<IntegrationCardFooter>
{token ? (
<>
<ConnectedActions onImport={handleImport} />
<ConnectedActions onImport={onImportClick} />
{openSetting && (
<SettingDialog
onClose={handleCloseSetting}

View File

@@ -18,6 +18,7 @@ import {
IntegrationSettingToggle,
} from '../setting';
import * as styles from './setting-dialog.css';
import { readwiseTrack } from './track';
export const SettingDialog = ({
onClose,
@@ -59,6 +60,18 @@ export const SettingDialog = ({
);
};
const trackModifySetting = (
item: 'New' | 'Update' | 'Tag',
option: 'on' | 'off',
method?: 'append' | 'override'
) => {
readwiseTrack.modifyIntegrationSettings({
item,
option,
method,
});
};
const Divider = () => {
return <li className={styles.divider} />;
};
@@ -72,6 +85,7 @@ const NewHighlightSetting = () => {
const toggle = useCallback(
(value: boolean) => {
trackModifySetting('New', value ? 'on' : 'off');
readwise.updateSetting('syncNewHighlights', value);
},
[readwise]
@@ -98,6 +112,7 @@ const UpdateStrategySetting = () => {
const toggle = useCallback(
(value: boolean) => {
trackModifySetting('Update', value ? 'on' : 'off', 'append');
if (!value) readwise.updateSetting('updateStrategy', undefined);
else readwise.updateSetting('updateStrategy', 'append');
},
@@ -106,6 +121,7 @@ const UpdateStrategySetting = () => {
const handleUpdate = useCallback(
(value: ReadwiseConfig['updateStrategy']) => {
trackModifySetting('Update', 'on', value);
readwise.updateSetting('updateStrategy', value);
},
[readwise]
@@ -167,12 +183,23 @@ const UpdateStrategySetting = () => {
const StartImport = ({ onImport }: { onImport: () => void }) => {
const t = useI18n();
const readwise = useService(IntegrationService).readwise;
const handleImport = useCallback(() => {
const lastImportedAt = readwise.setting$('lastImportedAt').value;
readwiseTrack.startIntegrationImport({
method: lastImportedAt ? 'withtimestamp' : 'new',
control: 'Readwise settings',
});
onImport();
}, [onImport, readwise]);
return (
<IntegrationSettingItem
name={t['com.affine.integration.readwise.setting.start-import-name']()}
desc={t['com.affine.integration.readwise.setting.start-import-desc']()}
>
<Button onClick={onImport}>
<Button onClick={handleImport}>
{t['com.affine.integration.readwise.setting.start-import-button']()}
</Button>
</IntegrationSettingItem>
@@ -226,18 +253,23 @@ const TagsSetting = () => {
);
const onSelectTag = useCallback(
(tagId: string) => {
trackModifySetting('Tag', 'on');
updateReadwiseTags([...(tagIds ?? []), tagId]);
},
[tagIds, updateReadwiseTags]
);
const onDeselectTag = useCallback(
(tagId: string) => {
trackModifySetting('Tag', 'off');
updateReadwiseTags(tagIds?.filter(id => id !== tagId) ?? []);
},
[tagIds, updateReadwiseTags]
);
const onDeleteTag = useCallback(
(tagId: string) => {
if (tagIds?.includes(tagId)) {
trackModifySetting('Tag', 'off');
}
tagService.tagList.deleteTag(tagId);
updateReadwiseTags(tagIds ?? []);
},

View File

@@ -0,0 +1,25 @@
import track from '@affine/track';
/**
* Wrap the track function to add default properties to the first argument
*/
export const readwiseTrack = new Proxy(track.$.settingsPanel.integrationList, {
get(target, key, receiver) {
const original = Reflect.get(target, key, receiver);
if (typeof original !== 'function') {
return original;
}
return function (this: unknown, ...args: unknown[]) {
if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) {
args[0] = {
type: 'readwise',
control: 'Readwise Card',
...args[0],
};
}
return original.apply(this, args);
};
},
});