mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-01 12:17:18 +00:00
create route file action
This commit is contained in:
1 parent
764d0b233a
commit
0f957fb1f2
60 files changed
+211
-85
No files matched your search
@@ -20,6 +20,12 @@ export function createFile() {
|
||||
currentTool.set(Tool.ROUTING);
|
||||
}
|
||||
|
||||
/** Creates a file that starts with an empty route, which the routing tool then draws. */
|
||||
export function createRouteFile() {
|
||||
engine.newFile(newFileName(), undefined, true);
|
||||
currentTool.set(Tool.ROUTING);
|
||||
}
|
||||
|
||||
/** Asks for the files to open: with the file picker of the system in the desktop app. */
|
||||
export async function openFiles() {
|
||||
if (get(engine.workspace)) {
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
closeFiles,
|
||||
closeFolder,
|
||||
createFile,
|
||||
createRouteFile,
|
||||
loadFiles,
|
||||
openFiles,
|
||||
openFolder,
|
||||
@@ -160,9 +161,13 @@
|
||||
<Menubar.Content class="border-none">
|
||||
<Menubar.Item onclick={createFile}>
|
||||
<Plus size="16" />
|
||||
{i18n._('menu.new')}
|
||||
{i18n._('menu.new_file')}
|
||||
<Shortcut key="+" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item onclick={createRouteFile}>
|
||||
<Plus size="16" />
|
||||
{i18n._('menu.new_route_file')}
|
||||
</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item onclick={openFiles}>
|
||||
<FolderOpen size="16" />
|
||||
|
||||
@@ -116,12 +116,16 @@
|
||||
/** The folder where what is made from the menu goes: the folder of the row. */
|
||||
let folder = $derived(kind === 'file' ? undefined : path);
|
||||
|
||||
/** Asks for the name of a file or a folder to make in the folder of the row (see `creating`). */
|
||||
function create(what: 'file' | 'folder') {
|
||||
/**
|
||||
* Asks for the name of a file or a folder to make in the folder of the row (see `creating`).
|
||||
* With `route`, the file starts with an empty route.
|
||||
*/
|
||||
function create(what: 'file' | 'folder', route = false) {
|
||||
if (folder === undefined) return;
|
||||
// where what is being named is shown
|
||||
folders.open(folder);
|
||||
creating.kind = what;
|
||||
creating.route = route;
|
||||
creating.parent = folder;
|
||||
}
|
||||
|
||||
@@ -286,6 +290,10 @@
|
||||
<Plus size="16" />
|
||||
{i18n._('menu.explorer_menu.new_file')}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onclick={() => create('file', true)}>
|
||||
<Plus size="16" />
|
||||
{i18n._('menu.explorer_menu.new_route_file')}
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onclick={() => create('folder')}>
|
||||
<Plus size="16" />
|
||||
{i18n._('menu.explorer_menu.new_folder')}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { CollapsibleTreeNode } from '$lib/components/collapsible-tree/index';
|
||||
import { FileText, Folder } from '@lucide/svelte';
|
||||
import { FileText, Folder, Route } from '@lucide/svelte';
|
||||
import { getContext } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -45,6 +45,7 @@
|
||||
async function create(value: string, retry: boolean) {
|
||||
const kind = pending;
|
||||
const parent = folder;
|
||||
const route = creating.route;
|
||||
if (kind === undefined || parent === undefined) return;
|
||||
const name = value.trim();
|
||||
const taken = entries.some(
|
||||
@@ -60,7 +61,7 @@
|
||||
if (name === '' || taken) return;
|
||||
let created: string | undefined;
|
||||
if (kind === 'file') {
|
||||
const id = await engine.newFileIn(name, parent);
|
||||
const id = await engine.newFileIn(name, parent, route);
|
||||
created = id === undefined ? undefined : get(workspace)?.files[id]?.path;
|
||||
if (created !== undefined) {
|
||||
currentTool.set(Tool.ROUTING);
|
||||
@@ -94,14 +95,20 @@
|
||||
<!-- the name of what is being made, which it is made with -->
|
||||
{#snippet named(kind: 'file' | 'folder')}
|
||||
<div class="flex flex-row items-center h-7 pl-1 pr-1 {kind === 'file' ? 'gap-1.5' : 'gap-1'}">
|
||||
{#if kind === 'file'}
|
||||
{#if kind === 'file' && creating.route}
|
||||
<Route size="16" class="shrink-0" />
|
||||
{:else if kind === 'file'}
|
||||
<FileText size="16" class="shrink-0" />
|
||||
{:else}
|
||||
<Folder size="16" class="shrink-0" />
|
||||
{/if}
|
||||
<NameInput
|
||||
placeholder={i18n._(
|
||||
kind === 'file' ? 'menu.explorer_menu.new_file' : 'menu.explorer_menu.new_folder'
|
||||
kind === 'folder'
|
||||
? 'menu.explorer_menu.new_folder'
|
||||
: creating.route
|
||||
? 'menu.explorer_menu.new_route_file'
|
||||
: 'menu.explorer_menu.new_file'
|
||||
)}
|
||||
oncommit={create}
|
||||
oncancel={() => (creating.parent = undefined)}
|
||||
|
||||
@@ -15,12 +15,14 @@ export const renaming: { path: string | undefined } = $state({ path: undefined }
|
||||
|
||||
/**
|
||||
* The file or folder that is being made in a folder (`parent`) of the explorer: it is only made once
|
||||
* the user gave it a name.
|
||||
* the user gave it a name. With `route`, the file starts with an empty route.
|
||||
*/
|
||||
export const creating: { parent: string | undefined; kind: 'file' | 'folder' } = $state({
|
||||
parent: undefined,
|
||||
kind: 'file',
|
||||
});
|
||||
export const creating: { parent: string | undefined; kind: 'file' | 'folder'; route: boolean } =
|
||||
$state({
|
||||
parent: undefined,
|
||||
kind: 'file',
|
||||
route: false,
|
||||
});
|
||||
|
||||
/** What is dragged in the explorer, and the folder it would be dropped in. */
|
||||
export const dragging: { paths: string[]; target: string | undefined } = $state({
|
||||
|
||||
@@ -54,7 +54,8 @@ export async function createTauriBackend(): Promise<Backend> {
|
||||
cut: () => invoke('cut'),
|
||||
paste: () => invoke('paste'),
|
||||
|
||||
new_file: (name, lng, lat, ele) => invoke('new_file', { name, lng, lat, ele }),
|
||||
new_file: (name, lng, lat, ele, route) =>
|
||||
invoke('new_file', { name, lng, lat, ele, route }),
|
||||
load_files: (files) => {
|
||||
const data = new Uint8Array(files.reduce((sum, file) => sum + file.data.length, 0));
|
||||
let offset = 0;
|
||||
@@ -225,7 +226,7 @@ export async function createTauriBackend(): Promise<Backend> {
|
||||
invoke<boolean>('export_files', { fileIds, options }),
|
||||
listFolder: (path) => invoke<FolderEntry[]>('list_folder', { path }),
|
||||
openUrl: (url) => invoke('open_url', { url }),
|
||||
newFile: (name, folder) => invoke<Outcome>('new_file', { name, folder }),
|
||||
newFile: (name, folder, route) => invoke<Outcome>('new_file', { name, folder, route }),
|
||||
createFolder: (parent, name) => invoke<string>('create_folder', { parent, name }),
|
||||
renamePath: (path, name) => invoke<Outcome>('rename_path', { path, name }),
|
||||
movePaths: (paths, folder) => invoke('move_paths', { paths, folder }),
|
||||
|
||||
@@ -100,8 +100,11 @@ export interface DesktopFiles {
|
||||
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>;
|
||||
/** Makes a new file, written at once in `folder` (a folder of the open folder), and selects it. */
|
||||
newFile(name: string, folder: string): Promise<Outcome>;
|
||||
/**
|
||||
* Makes a new file, written at once in `folder` (a folder of the open folder), and selects it.
|
||||
* With `route`, it starts with an empty route.
|
||||
*/
|
||||
newFile(name: string, folder: string, route: boolean): Promise<Outcome>;
|
||||
/** Makes a folder in a folder of the open folder (numbered if the name is taken): its path. */
|
||||
createFolder(parent: string, name: string): Promise<string>;
|
||||
/**
|
||||
@@ -165,7 +168,13 @@ export interface Backend {
|
||||
paste(): Promise<Outcome>;
|
||||
|
||||
// Files
|
||||
new_file(name: string, lng?: number, lat?: number, ele?: number): Promise<Outcome>;
|
||||
new_file(
|
||||
name: string,
|
||||
lng: number | undefined,
|
||||
lat: number | undefined,
|
||||
ele: number | undefined,
|
||||
route: boolean
|
||||
): Promise<Outcome>;
|
||||
load_files(files: { data: Uint8Array; name: string }[]): Promise<Outcome>;
|
||||
duplicate(): Promise<Outcome>;
|
||||
delete(wholeFiles: boolean): Promise<Outcome>;
|
||||
|
||||
@@ -204,6 +204,15 @@ describe('selection', () => {
|
||||
assert.equal(fileStates()[0].structure.routes.length, 3);
|
||||
});
|
||||
|
||||
it('creates a file that starts with an empty route', async () => {
|
||||
assert.equal(await engine.newFile('routes', undefined, true), true);
|
||||
const [state] = fileStates();
|
||||
assert.equal(state.structure.routes.length, 1);
|
||||
assert.equal(state.structure.routes[0].segment.length, 0);
|
||||
assert.equal(state.structure.tracks.length, 0);
|
||||
assert.deepEqual(get(engine.selection), { type: 'file', fileIds: [state.structure.id] });
|
||||
});
|
||||
|
||||
it('ignores what does not exist', async () => {
|
||||
await load('simple');
|
||||
const before = get(engine.selection);
|
||||
|
||||
@@ -251,11 +251,18 @@ class Engine {
|
||||
// Edits of the files
|
||||
|
||||
/**
|
||||
* Creates a file, which gets selected. With a `trackpoint`, the file starts with a track and a
|
||||
* segment that hold it, in the same undo step.
|
||||
* Creates a file, which gets selected. With `route`, the file starts with a route. With a
|
||||
* `trackpoint`, the file starts with a segment that holds it, the one of its route with `route`
|
||||
* or of a track otherwise, in the same undo step.
|
||||
*/
|
||||
newFile(name: string, trackpoint?: { lng: number; lat: number; ele?: number }) {
|
||||
return this.run((b) => b.new_file(name, trackpoint?.lng, trackpoint?.lat, trackpoint?.ele));
|
||||
newFile(
|
||||
name: string,
|
||||
trackpoint?: { lng: number; lat: number; ele?: number },
|
||||
route: boolean = false
|
||||
) {
|
||||
return this.run((b) =>
|
||||
b.new_file(name, trackpoint?.lng, trackpoint?.lat, trackpoint?.ele, route)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -606,10 +613,10 @@ class Engine {
|
||||
|
||||
/**
|
||||
* Makes a new file in a folder of the open folder, which is written there at once and selected.
|
||||
* Resolves to its id.
|
||||
* With `route`, it starts with an empty route. Resolves to its id.
|
||||
*/
|
||||
async newFileIn(name: string, folder: string): Promise<string | undefined> {
|
||||
const [id] = await this.addedBy((b) => b.desktop!.newFile(name, folder), true);
|
||||
async newFileIn(name: string, folder: string, route = false): Promise<string | undefined> {
|
||||
const [id] = await this.addedBy((b) => b.desktop!.newFile(name, folder, route), true);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user