fix: Fix native themes not working on external editors (#2957) [skip e2e]
This commit is contained in:
@@ -38,6 +38,9 @@ import {
|
|||||||
DocumentDirectoryPath,
|
DocumentDirectoryPath,
|
||||||
DownloadDirectoryPath,
|
DownloadDirectoryPath,
|
||||||
exists,
|
exists,
|
||||||
|
MainBundlePath,
|
||||||
|
readFile,
|
||||||
|
readFileAssets,
|
||||||
unlink,
|
unlink,
|
||||||
writeFile,
|
writeFile,
|
||||||
} from 'react-native-fs'
|
} from 'react-native-fs'
|
||||||
@@ -508,6 +511,15 @@ export class MobileDevice implements MobileDeviceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getNativeThemeCSS(identifier: string): Promise<string | undefined> {
|
||||||
|
let path = `Web.bundle/src/web-src/components/assets/${identifier}/index.css`
|
||||||
|
if (Platform.OS === 'ios') {
|
||||||
|
path = `${MainBundlePath}/${path}`
|
||||||
|
}
|
||||||
|
const content = Platform.OS === 'android' ? readFileAssets(path) : readFile(path)
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
async previewFile(base64: string, filename: string): Promise<boolean> {
|
async previewFile(base64: string, filename: string): Promise<boolean> {
|
||||||
const tempLocation = await this.downloadBase64AsFile(base64, filename, true)
|
const tempLocation = await this.downloadBase64AsFile(base64, filename, true)
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface MobileDeviceInterface extends DeviceInterface {
|
|||||||
handleThemeSchemeChange(isDark: boolean, bgColor: string): void
|
handleThemeSchemeChange(isDark: boolean, bgColor: string): void
|
||||||
shareBase64AsFile(base64: string, filename: string): Promise<void>
|
shareBase64AsFile(base64: string, filename: string): Promise<void>
|
||||||
downloadBase64AsFile(base64: string, filename: string, saveInTempLocation?: boolean): Promise<string | undefined>
|
downloadBase64AsFile(base64: string, filename: string, saveInTempLocation?: boolean): Promise<string | undefined>
|
||||||
|
getNativeThemeCSS(identifier: string): Promise<string | undefined>
|
||||||
previewFile(base64: string, filename: string): Promise<boolean>
|
previewFile(base64: string, filename: string): Promise<boolean>
|
||||||
exitApp(confirm?: boolean): void
|
exitApp(confirm?: boolean): void
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
NativeFeatureIdentifier,
|
NativeFeatureIdentifier,
|
||||||
GetDeprecatedEditors,
|
GetDeprecatedEditors,
|
||||||
} from '@standardnotes/features'
|
} from '@standardnotes/features'
|
||||||
import { Copy, removeFromArray, sleep, isNotUndefined, LoggerInterface } from '@standardnotes/utils'
|
import { Copy, removeFromArray, sleep, isNotUndefined, LoggerInterface, blobToBase64 } from '@standardnotes/utils'
|
||||||
import { ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
|
import { ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
|
||||||
import {
|
import {
|
||||||
AbstractService,
|
AbstractService,
|
||||||
@@ -90,6 +90,8 @@ export class ComponentManager
|
|||||||
this.items,
|
this.items,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private nativeThemesAsBase64: Record<string, string> = {}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private items: ItemManagerInterface,
|
private items: ItemManagerInterface,
|
||||||
private mutator: MutatorClientInterface,
|
private mutator: MutatorClientInterface,
|
||||||
@@ -109,6 +111,7 @@ export class ComponentManager
|
|||||||
this.addSyncedComponentItemObserver()
|
this.addSyncedComponentItemObserver()
|
||||||
this.registerMobileNativeComponentUrls()
|
this.registerMobileNativeComponentUrls()
|
||||||
this.registerDeprecatedEditorUrlsForAndroid()
|
this.registerDeprecatedEditorUrlsForAndroid()
|
||||||
|
void this.fetchNativeThemesOnMobile()
|
||||||
|
|
||||||
this.eventDisposers.push(
|
this.eventDisposers.push(
|
||||||
preferences.addEventObserver((event) => {
|
preferences.addEventObserver((event) => {
|
||||||
@@ -295,6 +298,29 @@ export class ComponentManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the native themes' CSS and stores them as `data:text/css;base64,...` URLs.
|
||||||
|
*/
|
||||||
|
private async fetchNativeThemesOnMobile(): Promise<void> {
|
||||||
|
if (!isMobileDevice(this.device)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for await (const theme of GetNativeThemes()) {
|
||||||
|
const css = await this.device.getNativeThemeCSS(theme.identifier)
|
||||||
|
if (css) {
|
||||||
|
const blob = new Blob([css], { type: 'text/css' })
|
||||||
|
const base64 = await blobToBase64(blob)
|
||||||
|
this.nativeThemesAsBase64[theme.identifier] = base64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
this.postActiveThemesToAllViewers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private registerDeprecatedEditorUrlsForAndroid(): void {
|
private registerDeprecatedEditorUrlsForAndroid(): void {
|
||||||
if (!isMobileDevice(this.device)) {
|
if (!isMobileDevice(this.device)) {
|
||||||
return
|
return
|
||||||
@@ -367,7 +393,19 @@ export class ComponentManager
|
|||||||
public urlsForActiveThemes(): string[] {
|
public urlsForActiveThemes(): string[] {
|
||||||
const themes = this.getActiveThemes()
|
const themes = this.getActiveThemes()
|
||||||
const urls = []
|
const urls = []
|
||||||
|
const isMobile = isMobileDevice(this.device)
|
||||||
for (const theme of themes) {
|
for (const theme of themes) {
|
||||||
|
if (isMobile && theme.isNativeFeature) {
|
||||||
|
/**
|
||||||
|
* Since native themes on mobile are stored in the app bundle and accessed as `file://` URLs,
|
||||||
|
* external editors cannot access them. To solve this, we store base64 encoded versions of the themes and send those to the editor instead of a file URL.
|
||||||
|
*/
|
||||||
|
const base64 = this.nativeThemesAsBase64[theme.featureIdentifier]
|
||||||
|
if (base64) {
|
||||||
|
urls.push(base64)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
const url = this.urlForFeature(theme)
|
const url = this.urlForFeature(theme)
|
||||||
if (url) {
|
if (url) {
|
||||||
urls.push(url)
|
urls.push(url)
|
||||||
|
|||||||
@@ -699,3 +699,17 @@ export function spaceSeparatedStrings(...strings: string[]): string {
|
|||||||
export function pluralize(count: number, singular: string, plural: string): string {
|
export function pluralize(count: number, singular: string, plural: string): string {
|
||||||
return count === 1 ? singular : plural
|
return count === 1 ? singular : plural
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function blobToBase64(blob: Blob): Promise<string> {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onloadend = () => {
|
||||||
|
if (reader.result && typeof reader.result === 'string') {
|
||||||
|
resolve(reader.result)
|
||||||
|
} else {
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(blob)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user