Skip to content

Commit

Permalink
feat: allow fast and slow import mode
Browse files Browse the repository at this point in the history
  • Loading branch information
linonetwo committed Dec 1, 2024
1 parent aeec4b8 commit 0d63c83
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 75 deletions.
4 changes: 3 additions & 1 deletion src/i18n/localization/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,9 @@
"TidGiMobileShare": "Shared from TidGi Mobile",
"ImportSuccess": "Import Success",
"TagForSharedContent": "Tag for shared content",
"Clipped": "Clipped"
"Clipped": "Clipped",
"FastImport": "Fast Import",
"FastImportDescription": "Import to default workspace without opening the wiki, but no popup box to confirm. You can manually open the wiki and view the imported results in “Recent”."
},
"Description": "Description",
"Tags": "Tags",
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/localization/locales/zh_CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,9 @@
"ImportSuccess": "导入成功",
"TagForSharedContent": "分享内容标签",
"TagForSharedContentDescription": "分享给太记移动端的内容,会自动加上这个标签。留空时表示不加任何标签。",
"Clipped": "剪藏"
"Clipped": "剪藏",
"FastImport": "快速导入",
"FastImportDescription": "无需打开Wiki即可导入内容到默认工作区,但不会有弹框确认。可以手动打开Wiki后到「最近」里查看导入的结果。"
},
"Import": {
"ImportWiki": "导入Wiki"
Expand Down
17 changes: 15 additions & 2 deletions src/pages/Config/Shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { styled } from 'styled-components/native';

import { TextInput, Text } from 'react-native-paper';
import { Switch, Text, TextInput } from 'react-native-paper';
import { FlexibleText, SwitchContainer } from '../../components/PreferenceWidgets';
import { useConfigStore } from '../../store/config';

const StyledTextInput = styled(TextInput)`
Expand All @@ -13,14 +14,18 @@ const StyledTextInput = styled(TextInput)`
export function Shared(): JSX.Element {
const { t } = useTranslation();

const [initialTagForSharedContent] = useConfigStore(state => [state.tagForSharedContent]);
const [initialTagForSharedContent, fastImport] = useConfigStore(state => [state.tagForSharedContent, state.fastImport]);
const [tagForSharedContent, tagForSharedContentSetter] = useState(initialTagForSharedContent);
const setConfig = useConfigStore(state => state.set);

const tagForSharedContentOnChange = useDebouncedCallback((newText: string) => {
setConfig({ tagForSharedContent: newText });
}, []);

const fastImportOnChange = (value: boolean) => {
setConfig({ fastImport: value });
};

return (
<>
<StyledTextInput
Expand All @@ -33,6 +38,14 @@ export function Shared(): JSX.Element {
}}
/>
<Text>{t('Share.TagForSharedContentDescription')}</Text>
<Text variant='titleLarge'>{t('Share.FastImport')}</Text>
<SwitchContainer>
<FlexibleText>{t('Share.FastImportDescription')}</FlexibleText>
<Switch
value={fastImport}
onValueChange={fastImportOnChange}
/>
</SwitchContainer>
</>
);
}
49 changes: 2 additions & 47 deletions src/services/NativeService/hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
/* eslint-disable security-node/detect-crlf */
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { format } from 'date-fns';
import * as fs from 'expo-file-system';
import type { useShareIntent as IUseShareIntent } from 'expo-share-intent';
import { compact } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { Snackbar } from 'react-native-paper';
import { useRegisterProxy } from 'react-native-postmessage-cat';
import { ITiddlerFieldsParam } from 'tiddlywiki';

import i18n from '../../i18n';
import { useConfigStore } from '../../store/config';
import { IWikiWorkspace, useWorkspaceStore } from '../../store/workspace';
import { WikiStorageService } from '../WikiStorageService';
import { nativeService } from '.';
import { NativeServiceIPCDescriptor } from './descriptor';

Expand Down Expand Up @@ -42,8 +35,6 @@ export function useRegisterReceivingShareIntent() {
{i18n.t('Share.ImportSuccess')}
</Snackbar>
);
const [tagForSharedContent] = useConfigStore(state => [state.tagForSharedContent]);
const newTagForSharedContent = useMemo(() => tagForSharedContent ?? i18n.t('Share.Clipped'), [tagForSharedContent]);

/** If you get error on development:
* ```
Expand Down Expand Up @@ -75,44 +66,8 @@ export function useRegisterReceivingShareIntent() {
void (async () => {
try {
if (!hasShareIntent) return;
const defaultWiki = compact(useWorkspaceStore.getState().workspaces).find((w): w is IWikiWorkspace => w.type === 'wiki');
if (defaultWiki === undefined) return;
await nativeService.receivingShareIntent(shareIntent);
resetShareIntent();
// put into default workspace's database, with random title
const storageOfDefaultWorkspace = new WikiStorageService(defaultWiki);
const randomTitle = `${i18n.t('Share.SharedContent')}-${Date.now()}`;
const created = format(new Date(), 'yyyyMMddHHmmssSSS');
let fields: ITiddlerFieldsParam = {
created,
modified: created,
creator: i18n.t('Share.TidGiMobileShare'),
tags: newTagForSharedContent,
};
if (shareIntent.webUrl) fields = { ...fields, url: shareIntent.webUrl };
switch (shareIntent.type) {
case 'text':
case 'weburl': {
if (shareIntent.text) fields = { ...fields, text: shareIntent.text };
await storageOfDefaultWorkspace.saveTiddler(shareIntent.meta?.title ?? randomTitle, fields);
break;
}
case 'media':
case 'file': {
if (shareIntent.files) {
for (const file of shareIntent.files) {
const fileContent = await fs.readAsStringAsync(file.path, { encoding: fs.EncodingType.Base64 });
const fileFields = {
...fields,
type: file.mimeType,
text: fileContent,
};
await storageOfDefaultWorkspace.saveTiddler(file.fileName || randomTitle, fileFields);
}
}
break;
}
}
setImportSuccessSnackBarVisible(true);
} catch (error) {
console.log(
Expand All @@ -122,7 +77,7 @@ export function useRegisterReceivingShareIntent() {
);
}
})();
}, [hasShareIntent, shareIntent, resetShareIntent, error, newTagForSharedContent]);
}, [hasShareIntent, shareIntent, resetShareIntent, error]);

return { importSuccessSnackBar };
}
104 changes: 81 additions & 23 deletions src/services/NativeService/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { format } from 'date-fns';
import { Camera, PermissionStatus } from 'expo-camera';
import * as fs from 'expo-file-system';
import { ShareIntent } from 'expo-share-intent';
import type { ShareIntent } from 'expo-share-intent';
import { compact } from 'lodash';

import type { ITiddlerFieldsParam } from 'tiddlywiki';
import { openDefaultWikiIfNotAlreadyThere } from '../../hooks/useAutoOpenDefaultWiki';
import i18n from '../../i18n';
import { useConfigStore } from '../../store/config';
import { IWikiWorkspace, useWorkspaceStore } from '../../store/workspace';
import type { WikiHookService } from '../WikiHookService';
import { WikiStorageService } from '../WikiStorageService';
import { importBinaryTiddlers, importTextTiddlers } from './wikiOperations';

/**
Expand Down Expand Up @@ -73,34 +80,40 @@ export class NativeService {
}
}

async receivingShareIntent(shareIntent: ShareIntent) {
const configs = useConfigStore.getState();
if (configs.fastImport) {
await this.storeSharedContentToStorage(shareIntent);
} else {
await this.importSharedContentToWiki(shareIntent);
}
}

/**
* If wiki has not started, android will store files in a queue, wait for getReceivedFiles to be called.
* Even wiki previously loaded, but after go background for a while, it may be unloaded too. We need to wait not only webview loaded, need wiki core loaded, then call this.
*/
async receivingShareIntent(shareIntent: ShareIntent) {
// wait for wiki start, and use injectJavascript to add tiddler, for user to edit.
// To get All Recived Urls

// files returns as JSON Array example
// [{ filePath: null, text: null, weblink: null, mimeType: null, contentUri: null, fileName: null, extension: null }]
async importSharedContentToWiki(shareIntent: ShareIntent) {
const { text, files } = shareIntent;
let script = '';
if (files !== null) {
console.log(text, files);
if (text) {
script = importTextTiddlers([text]);
} else {
if (files.length === 0) return;
const filesWithFileLoadedToText = await Promise.all(files.map(async (file) => {
if (file.path === null) return file;
/**
* based on tiddlywiki file type parsers `$tw.utils.registerFileType("image/jpeg","base64",[".jpg",".jpeg"],{flags:["image"]});`
* we need to use base64 encoding to load file
*/
const text = await fs.readAsStringAsync(file.path.startsWith('file://') ? file.path : `file://${file.path}`, { encoding: 'base64' });
return { ...file, text };
}));
script = importBinaryTiddlers(filesWithFileLoadedToText);
switch (shareIntent.type) {
case 'text':
case 'weburl': {
if (text) {
script = importTextTiddlers([text]);
}
break;
}
case 'media':
case 'file': {
if (files && files.length > 0) {
const filesWithFileLoadedToText = await Promise.all(files.map(async (file) => {
const text = await fs.readAsStringAsync(file.path.startsWith('file://') ? file.path : `file://${file.path}`, { encoding: 'base64' });
return { ...file, text, type: file.mimeType };
}));
script = importBinaryTiddlers(filesWithFileLoadedToText);
}
break;
}
}
if (!script) return;
Expand All @@ -109,6 +122,51 @@ export class NativeService {
await wikiHookService.executeAfterTwReady(script);
}

/**
* Directly store shared content to default workspace's sqlite, very fast, don't need to wait for wiki to load.
*/
async storeSharedContentToStorage(shareIntent: ShareIntent) {
const defaultWiki = compact(useWorkspaceStore.getState().workspaces).find((w): w is IWikiWorkspace => w.type === 'wiki');
if (defaultWiki === undefined) return;
const storageOfDefaultWorkspace = new WikiStorageService(defaultWiki);
const configs = useConfigStore.getState();
const tagForSharedContent = configs.tagForSharedContent;
const newTagForSharedContent = tagForSharedContent ?? i18n.t('Share.Clipped');
// put into default workspace's database, with random title
const randomTitle = `${i18n.t('Share.SharedContent')}-${Date.now()}`;
const created = format(new Date(), 'yyyyMMddHHmmssSSS');
let fields: ITiddlerFieldsParam = {
created,
modified: created,
creator: i18n.t('Share.TidGiMobileShare'),
tags: newTagForSharedContent,
};
if (shareIntent.webUrl) fields = { ...fields, url: shareIntent.webUrl };
switch (shareIntent.type) {
case 'text':
case 'weburl': {
if (shareIntent.text) fields = { ...fields, text: shareIntent.text };
await storageOfDefaultWorkspace.saveTiddler(shareIntent.meta?.title ?? randomTitle, fields);
break;
}
case 'media':
case 'file': {
if (shareIntent.files) {
for (const file of shareIntent.files) {
const fileContent = await fs.readAsStringAsync(file.path.startsWith('file://') ? file.path : `file://${file.path}`, { encoding: fs.EncodingType.Base64 });
const fileFields = {
...fields,
type: file.mimeType,
text: fileContent,
};
await storageOfDefaultWorkspace.saveTiddler(file.fileName || randomTitle, fileFields);
}
}
break;
}
}
}

async saveFileToFs(filename: string, text: string, mimeType?: string): Promise<string | false> {
const configs = useConfigStore.getState();
const result = await fs.StorageAccessFramework.requestDirectoryPermissionsAsync(configs.defaultDownloadLocation);
Expand Down
4 changes: 3 additions & 1 deletion src/store/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface ConfigState {
/** the initial value should be undefined, so an initial true value won't immediately trigger autoOpen */
autoOpenDefaultWiki?: boolean;
defaultDownloadLocation?: string;
fastImport: boolean;
hideStatusBar?: boolean;
keepAliveInBackground: boolean;
preferredLanguage?: string;
Expand All @@ -23,16 +24,17 @@ export interface ConfigState {
}
const defaultConfig: ConfigState = {
autoOpenDefaultWiki: undefined,
fastImport: true,
hideStatusBar: false,
keepAliveInBackground: true,
preferredLanguage: undefined,
rememberLastVisitState: true,
syncInBackground: true,
syncInterval: 60 * 1000,
syncIntervalBackground: 60 * 30 * 1000,
tagForSharedContent: undefined,
theme: 'default',
translucentStatusBar: true,
tagForSharedContent: undefined,
userName: '',
};
interface ConfigActions {
Expand Down

0 comments on commit 0d63c83

Please sign in to comment.