parent
77859ea0ca
commit
83c359e2e7
|
@ -98,6 +98,9 @@ class PageController extends Controller
|
||||||
$addImageDomain('https://*.tile.openstreetmap.org');
|
$addImageDomain('https://*.tile.openstreetmap.org');
|
||||||
$addImageDomain('https://*.a.ssl.fastly.net');
|
$addImageDomain('https://*.a.ssl.fastly.net');
|
||||||
|
|
||||||
|
// Native communication
|
||||||
|
$addImageDomain('http://127.0.0.1');
|
||||||
|
|
||||||
// Allow Nominatim
|
// Allow Nominatim
|
||||||
$policy->addAllowedConnectDomain('nominatim.openstreetmap.org');
|
$policy->addAllowedConnectDomain('nominatim.openstreetmap.org');
|
||||||
|
|
||||||
|
|
|
@ -66,13 +66,6 @@ export async function fetchImage(url: string) {
|
||||||
let entry = BLOB_CACHE.get(url);
|
let entry = BLOB_CACHE.get(url);
|
||||||
if (entry) return entry[1];
|
if (entry) return entry[1];
|
||||||
|
|
||||||
// Check if native image
|
|
||||||
if (nativex.IS_NATIVE_URL(url)) {
|
|
||||||
const dataUri = await nativex.getJpegDataUri(url);
|
|
||||||
BLOB_CACHE.set(url, [60, dataUri]);
|
|
||||||
return dataUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch image
|
// Fetch image
|
||||||
const blobUrl = await importer<typeof w.fetchImageSrc>('fetchImageSrc')(url);
|
const blobUrl = await importer<typeof w.fetchImageSrc>('fetchImageSrc')(url);
|
||||||
|
|
||||||
|
|
|
@ -775,7 +775,7 @@ export default defineComponent({
|
||||||
const fullUrl = isvideo
|
const fullUrl = isvideo
|
||||||
? null
|
? null
|
||||||
: photo.flag & this.c.FLAG_IS_LOCAL
|
: photo.flag & this.c.FLAG_IS_LOCAL
|
||||||
? nativex.NATIVE_URL_FULL(photo.fileid)
|
? nativex.API.IMAGE_FULL(photo.fileid)
|
||||||
: API.IMAGE_DECODABLE(photo.fileid, photo.etag);
|
: API.IMAGE_DECODABLE(photo.fileid, photo.etag);
|
||||||
const fullLoadCond = this.config.full_res_always ? 'always' : this.config.full_res_on_zoom ? 'zoom' : 'never';
|
const fullLoadCond = this.config.full_res_always ? 'always' : this.config.full_res_on_zoom ? 'zoom' : 'never';
|
||||||
|
|
||||||
|
|
146
src/native.ts
146
src/native.ts
|
@ -1,9 +1,13 @@
|
||||||
import type { IDay, IPhoto } from './types';
|
import type { IDay, IPhoto } from './types';
|
||||||
|
|
||||||
/**
|
const BASE_URL = 'http://127.0.0.1';
|
||||||
* Type of a native promise (this will be the exact type in Java).
|
|
||||||
*/
|
export const API = {
|
||||||
type NativePromise<T> = (call: string, arg: T) => void;
|
DAYS: () => `${BASE_URL}/api/days`,
|
||||||
|
DAY: (dayId: number) => `${BASE_URL}/api/days/${dayId}`,
|
||||||
|
IMAGE_PREVIEW: (fileId: number) => `${BASE_URL}/image/preview/${fileId}`,
|
||||||
|
IMAGE_FULL: (fileId: number) => `${BASE_URL}/image/full/${fileId}`,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Native interface for the Android app.
|
* Native interface for the Android app.
|
||||||
|
@ -11,71 +15,11 @@ type NativePromise<T> = (call: string, arg: T) => void;
|
||||||
export type NativeX = {
|
export type NativeX = {
|
||||||
isNative: () => boolean;
|
isNative: () => boolean;
|
||||||
setThemeColor: (color: string, isDark: boolean) => void;
|
setThemeColor: (color: string, isDark: boolean) => void;
|
||||||
getLocalDays: NativePromise<string>;
|
|
||||||
getLocalByDayId: NativePromise<string>;
|
|
||||||
getJpeg: NativePromise<string>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** The native interface is a global object that is injected by the native app. */
|
/** The native interface is a global object that is injected by the native app. */
|
||||||
const nativex: NativeX = globalThis.nativex;
|
const nativex: NativeX = globalThis.nativex;
|
||||||
|
|
||||||
/** List of promises that are waiting for a native response. */
|
|
||||||
const nativePromises = new Map<string, object>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a native function in a promise.
|
|
||||||
* JavascriptInterface doesn't support async functions, so we have to do this manually.
|
|
||||||
* The native function should call `window.nativexr(call, resolve, reject)` when it's done.
|
|
||||||
*
|
|
||||||
* @param fun Function to promisify
|
|
||||||
* @param binary Whether the response is binary (will not be decoded)
|
|
||||||
*/
|
|
||||||
function nativePromisify<A, T>(fun: NativePromise<A>, binary = false): (arg: A) => Promise<T> {
|
|
||||||
if (!fun) {
|
|
||||||
return () => {
|
|
||||||
return new Promise((_, reject) => {
|
|
||||||
reject('Native function not available');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return (arg: A) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const call = Math.random().toString(36).substring(7);
|
|
||||||
nativePromises.set(call, { resolve, reject, binary });
|
|
||||||
fun(call, arg);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the global handler for native responses.
|
|
||||||
* This should be called by the native app when it's ready to resolve a promise.
|
|
||||||
*
|
|
||||||
* @param call ID passed to native function
|
|
||||||
* @param resolve Response from native function
|
|
||||||
* @param reject Rejection from native function
|
|
||||||
*/
|
|
||||||
globalThis.nativexr = (call: string, resolve?: string, reject?: string) => {
|
|
||||||
const promise = nativePromises.get(call);
|
|
||||||
if (!promise) {
|
|
||||||
console.error('No promise found for call', call);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolve !== undefined) {
|
|
||||||
if (!(promise as any).binary) resolve = window.atob(resolve);
|
|
||||||
(promise as any).resolve(resolve);
|
|
||||||
} else if (reject !== undefined) {
|
|
||||||
(promise as any).reject(window.atob(reject));
|
|
||||||
} else {
|
|
||||||
console.error('No resolve or reject found for call', call);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nativePromises.delete(call);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns Whether the native interface is available.
|
* @returns Whether the native interface is available.
|
||||||
*/
|
*/
|
||||||
|
@ -95,29 +39,6 @@ export const setTheme = (color?: string, dark?: boolean) => {
|
||||||
nativex?.setThemeColor?.(color, dark);
|
nativex?.setThemeColor?.(color, dark);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the local days array.
|
|
||||||
*
|
|
||||||
* @returns List of local days (JSON string)
|
|
||||||
*/
|
|
||||||
const getLocalDays = nativePromisify<number, string>(nativex?.getLocalDays.bind(nativex));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the local photos for a day with a dayId.
|
|
||||||
*
|
|
||||||
* @param dayId Day ID to get photos for
|
|
||||||
* @returns List of local photos (JSON string)
|
|
||||||
*/
|
|
||||||
const getLocalByDayId = nativePromisify<number, string>(nativex?.getLocalByDayId.bind(nativex));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the JPEG data for a photo using a local URI.
|
|
||||||
*
|
|
||||||
* @param url Local URI to get JPEG data for
|
|
||||||
* @returns JPEG data (base64 string)
|
|
||||||
*/
|
|
||||||
const getJpeg = nativePromisify<string, string>(nativex?.getJpeg.bind(nativex), true);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend a list of days with local days.
|
* Extend a list of days with local days.
|
||||||
* Fetches the local days from the native interface.
|
* Fetches the local days from the native interface.
|
||||||
|
@ -126,7 +47,9 @@ export async function extendDaysWithLocal(days: IDay[]) {
|
||||||
if (!has()) return;
|
if (!has()) return;
|
||||||
|
|
||||||
// Query native part
|
// Query native part
|
||||||
const local: IDay[] = JSON.parse(await getLocalDays(0));
|
const res = await fetch(API.DAYS());
|
||||||
|
if (!res.ok) return;
|
||||||
|
const local: IDay[] = await res.json();
|
||||||
const remoteMap = new Map(days.map((d) => [d.dayid, d]));
|
const remoteMap = new Map(days.map((d) => [d.dayid, d]));
|
||||||
|
|
||||||
// Merge local days into remote days
|
// Merge local days into remote days
|
||||||
|
@ -155,49 +78,14 @@ export async function extendDaysWithLocal(days: IDay[]) {
|
||||||
export async function extendDayWithLocal(dayId: number, photos: IPhoto[]) {
|
export async function extendDayWithLocal(dayId: number, photos: IPhoto[]) {
|
||||||
if (!has()) return;
|
if (!has()) return;
|
||||||
|
|
||||||
const localPhotos: IPhoto[] = JSON.parse(await getLocalByDayId(dayId));
|
// Query native part
|
||||||
|
const res = await fetch(API.DAY(dayId));
|
||||||
|
if (!res.ok) return;
|
||||||
|
|
||||||
|
// Merge local photos into remote photos
|
||||||
|
const localPhotos: IPhoto[] = await res.json();
|
||||||
const photosSet = new Set(photos.map((p) => p.basename));
|
const photosSet = new Set(photos.map((p) => p.basename));
|
||||||
const localOnly = localPhotos.filter((p) => !photosSet.has(p.basename));
|
const localOnly = localPhotos.filter((p) => !photosSet.has(p.basename));
|
||||||
localOnly.forEach((p) => (p.islocal = true));
|
localOnly.forEach((p) => (p.islocal = true));
|
||||||
photos.push(...localOnly);
|
photos.push(...localOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the JPEG data URI for a photo using a native URI.
|
|
||||||
*
|
|
||||||
* @param url Native URI to get JPEG data for
|
|
||||||
* @returns Data URI for JPEG
|
|
||||||
*/
|
|
||||||
export async function getJpegDataUri(url: string) {
|
|
||||||
const image = await getJpeg(url);
|
|
||||||
return `data:image/jpeg;base64,${image}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a URL is a native URI (nativex://).
|
|
||||||
*
|
|
||||||
* @param url URL to check
|
|
||||||
*/
|
|
||||||
export function IS_NATIVE_URL(url: string) {
|
|
||||||
return url.startsWith('nativex://');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a downsized preview URL for a native file ID.
|
|
||||||
*
|
|
||||||
* @param fileid Local file ID returned by native interface
|
|
||||||
* @returns native URI
|
|
||||||
*/
|
|
||||||
export function NATIVE_URL_PREVIEW(fileid: number) {
|
|
||||||
return `nativex://preview/${fileid}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a full sized URL for a native file ID.
|
|
||||||
*
|
|
||||||
* @param fileid Local file ID returned by native interface
|
|
||||||
* @returns native URI
|
|
||||||
*/
|
|
||||||
export function NATIVE_URL_FULL(fileid: number) {
|
|
||||||
return `nativex://full/${fileid}`;
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function isMobile() {
|
||||||
export function getPreviewUrl(photo: IPhoto, square: boolean, size: number | [number, number] | 'screen') {
|
export function getPreviewUrl(photo: IPhoto, square: boolean, size: number | [number, number] | 'screen') {
|
||||||
// Native preview
|
// Native preview
|
||||||
if (photo.flag & constants.c.FLAG_IS_LOCAL) {
|
if (photo.flag & constants.c.FLAG_IS_LOCAL) {
|
||||||
return nativex.NATIVE_URL_PREVIEW(photo.fileid);
|
return nativex.API.IMAGE_PREVIEW(photo.fileid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screen-appropriate size
|
// Screen-appropriate size
|
||||||
|
|
Loading…
Reference in New Issue