mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-19 09:22:12 +00:00
open links in browser
This commit is contained in:
1 parent
8d6885d58b
commit
358253b7a9
9 files changed
+90
No files matched your search
@@ -25,4 +25,5 @@ notify = "8.2.0"
|
|||||||
notify-debouncer-mini = "0.7.0"
|
notify-debouncer-mini = "0.7.0"
|
||||||
tauri-plugin-dialog = "2.8.2"
|
tauri-plugin-dialog = "2.8.2"
|
||||||
tauri-plugin-single-instance = "2.5.3"
|
tauri-plugin-single-instance = "2.5.3"
|
||||||
|
tauri-plugin-opener = "2"
|
||||||
|
|
||||||
@@ -12,3 +12,4 @@ pub mod queries;
|
|||||||
pub mod selection;
|
pub mod selection;
|
||||||
pub mod statistics;
|
pub mod statistics;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
|
pub mod system;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//! What the app asks of the system, outside of the files.
|
||||||
|
|
||||||
|
use tauri::{AppHandle, Runtime};
|
||||||
|
use tauri_plugin_opener::OpenerExt;
|
||||||
|
|
||||||
|
/// Opens a web page (or an email to write) with the app of the system for it: the links of the
|
||||||
|
/// page are not followed in the window of the app. Only `http`, `https` and `mailto` addresses are
|
||||||
|
/// opened.
|
||||||
|
#[tauri::command(async)]
|
||||||
|
pub fn open_url<R: Runtime>(app: AppHandle<R>, url: String) -> Result<(), String> {
|
||||||
|
let allowed = ["http://", "https://", "mailto:"];
|
||||||
|
if !allowed.iter().any(|scheme| url.starts_with(scheme)) {
|
||||||
|
return Err(format!("{url} is not a web address"));
|
||||||
|
}
|
||||||
|
app.opener()
|
||||||
|
.open_url(url, None::<&str>)
|
||||||
|
.map_err(|error| error.to_string())
|
||||||
|
}
|
||||||
@@ -50,6 +50,7 @@ pub fn run() {
|
|||||||
));
|
));
|
||||||
register_commands(builder)
|
register_commands(builder)
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.plugin(tauri_plugin_opener::init())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let handle = app.handle().clone();
|
let handle = app.handle().clone();
|
||||||
let emit: state::Emit = std::sync::Arc::new(move |event, payload| {
|
let emit: state::Emit = std::sync::Arc::new(move |event, payload| {
|
||||||
@@ -193,6 +194,8 @@ fn register_commands<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::Bu
|
|||||||
disk::reload_files,
|
disk::reload_files,
|
||||||
disk::export_files,
|
disk::export_files,
|
||||||
disk::list_folder,
|
disk::list_folder,
|
||||||
|
// the system
|
||||||
|
system::open_url,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { engine } from '$lib/engine';
|
||||||
|
|
||||||
|
/** Where the pages of the website are, for the links of the desktop app to them. */
|
||||||
|
const website = 'https://gpx.studio';
|
||||||
|
|
||||||
|
let installed = false;
|
||||||
|
|
||||||
|
/** The address to open in the browser for a link: the pages of the website are on the website. */
|
||||||
|
function browserURL(href: string): string {
|
||||||
|
const url = new URL(href, location.href);
|
||||||
|
return url.origin === location.origin
|
||||||
|
? `${website}${url.pathname}${url.search}${url.hash}`
|
||||||
|
: url.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the links that leave the app in the browser of the system, as the window of the desktop app
|
||||||
|
* does not follow them: the external links, the links that open a new tab (the pages of the
|
||||||
|
* website, like the help), and what `window.open` is given. The other links are pages of the app.
|
||||||
|
* It is done once, for as long as the window is open.
|
||||||
|
*/
|
||||||
|
export function openLinksInBrowser() {
|
||||||
|
if (installed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
installed = true;
|
||||||
|
const onClick = (e: MouseEvent) => {
|
||||||
|
// the main button, or the middle one which opens a new tab
|
||||||
|
if (e.button > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const link = (e.target as Element | null)?.closest?.('a[href]');
|
||||||
|
if (!(link instanceof HTMLAnchorElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = new URL(link.href, location.href);
|
||||||
|
if (url.origin === location.origin && link.target !== '_blank' && e.button === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
engine.openUrl(browserURL(link.href));
|
||||||
|
};
|
||||||
|
window.open = (url?: string | URL) => {
|
||||||
|
if (url !== undefined) {
|
||||||
|
engine.openUrl(browserURL(String(url)));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
// before the router of the app, which would follow the link
|
||||||
|
document.addEventListener('click', onClick, true);
|
||||||
|
document.addEventListener('auxclick', onClick, true);
|
||||||
|
}
|
||||||
@@ -221,6 +221,7 @@ export async function createTauriBackend(): Promise<Backend> {
|
|||||||
exportFiles: (fileIds, options) =>
|
exportFiles: (fileIds, options) =>
|
||||||
invoke<boolean>('export_files', { fileIds, options }),
|
invoke<boolean>('export_files', { fileIds, options }),
|
||||||
listFolder: (path) => invoke<FolderEntry[]>('list_folder', { path }),
|
listFolder: (path) => invoke<FolderEntry[]>('list_folder', { path }),
|
||||||
|
openUrl: (url) => invoke('open_url', { url }),
|
||||||
listen: async (onEngineChanged, onWorkspaceChanged, onFilesOpened, onFolderChanged) => {
|
listen: async (onEngineChanged, onWorkspaceChanged, onFilesOpened, onFolderChanged) => {
|
||||||
await listen<Outcome>('files-opened', (event) => onFilesOpened(event.payload));
|
await listen<Outcome>('files-opened', (event) => onFilesOpened(event.payload));
|
||||||
await listen<Outcome>('engine-changed', (event) => onEngineChanged(event.payload));
|
await listen<Outcome>('engine-changed', (event) => onEngineChanged(event.payload));
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ export interface DesktopFiles {
|
|||||||
exportFiles(fileIds: string[], options: ExportOptions): Promise<boolean>;
|
exportFiles(fileIds: string[], options: ExportOptions): Promise<boolean>;
|
||||||
/** The folders and GPX files in a folder of the open folder (the open folder by default). */
|
/** The folders and GPX files in a folder of the open folder (the open folder by default). */
|
||||||
listFolder(path?: string): Promise<FolderEntry[]>;
|
listFolder(path?: string): Promise<FolderEntry[]>;
|
||||||
|
/** Opens a web page (or an email to write) in the app of the system for it. */
|
||||||
|
openUrl(url: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Tells when the engine changed without being asked, because the files on the disk did (the
|
* Tells when the engine changed without being asked, because the files on the disk did (the
|
||||||
* outcome to apply), when the status of the files changed, when files were opened with the
|
* outcome to apply), when the status of the files changed, when files were opened with the
|
||||||
|
|||||||
@@ -617,6 +617,16 @@ class Engine {
|
|||||||
await this.enqueue(async () => this.refreshWorkspace(this.backend!));
|
await this.enqueue(async () => this.refreshWorkspace(this.backend!));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Opens a web page in the browser of the system (the desktop app). */
|
||||||
|
async openUrl(url: string) {
|
||||||
|
await this.ready;
|
||||||
|
try {
|
||||||
|
await this.backend!.desktop?.openUrl(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('The page could not be opened', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** The folders and GPX files in a folder of the open folder (the open folder by default). */
|
/** The folders and GPX files in a folder of the open folder (the open folder by default). */
|
||||||
async listFolder(path?: string): Promise<FolderEntry[]> {
|
async listFolder(path?: string): Promise<FolderEntry[]> {
|
||||||
await this.ready;
|
await this.ready;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { getURLForLanguage } from '$lib/utils';
|
import { getURLForLanguage } from '$lib/utils';
|
||||||
import { inDesktopApp } from '$lib/engine/backend';
|
import { inDesktopApp } from '$lib/engine/backend';
|
||||||
|
import { openLinksInBrowser } from '$lib/desktop/links';
|
||||||
import { hoveredPoint, slicedStatistics } from '$lib/state/selection-statistics';
|
import { hoveredPoint, slicedStatistics } from '$lib/state/selection-statistics';
|
||||||
import { engine } from '$lib/engine';
|
import { engine } from '$lib/engine';
|
||||||
import { boundsManager } from '$lib/state/bounds';
|
import { boundsManager } from '$lib/state/bounds';
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
settings.connect(await engine.openStorage());
|
settings.connect(await engine.openStorage());
|
||||||
|
|
||||||
if (inDesktopApp()) {
|
if (inDesktopApp()) {
|
||||||
|
openLinksInBrowser();
|
||||||
// the window of the desktop app always opens the same page, in English: it goes to the
|
// the window of the desktop app always opens the same page, in English: it goes to the
|
||||||
// language that was chosen last
|
// language that was chosen last
|
||||||
const saved = get(settings.language);
|
const saved = get(settings.language);
|
||||||
|
|||||||
Reference in new issue
Block a user