diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 97c39c624b..48379b8992 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -126,6 +126,10 @@ pub enum PortfolioMessage { layers: Vec, }, PrevDocument, + ReorderDocument { + document_id: DocumentId, + new_index: usize, + }, RequestWelcomeScreenButtonsLayout, RequestStatusBarInfoLayout, SetActivePanel { diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 8254300b0f..ba72bfdfbd 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -1053,6 +1053,24 @@ impl MessageHandler> for Portfolio responses.add(PortfolioMessage::SelectDocument { document_id: prev_id }); } } + PortfolioMessage::ReorderDocument { document_id, new_index } => { + let new_index = new_index.min(self.document_ids.len().saturating_sub(1)); + let Some(current_index) = self.document_ids.iter().position(|&id| id == document_id) else { + return; + }; + + if new_index != current_index { + self.document_ids.remove(current_index); + self.document_ids.insert(new_index, document_id); + + responses.add(PortfolioMessage::UpdateOpenDocumentsList); + + // Re-send the active document so the frontend recalculates the active tab index after reordering + if let Some(active_document_id) = self.active_document_id { + responses.add(FrontendMessage::UpdateActiveDocument { document_id: active_document_id }); + } + } + } PortfolioMessage::RequestWelcomeScreenButtonsLayout => { let donate = "https://graphite.art/donate/"; diff --git a/frontend/src/components/window/Panel.svelte b/frontend/src/components/window/Panel.svelte index d36278c5e7..df17cbbbb2 100644 --- a/frontend/src/components/window/Panel.svelte +++ b/frontend/src/components/window/Panel.svelte @@ -1,5 +1,5 @@ panelType && editor.setActivePanel(panelType)} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}> - + {#each tabLabels as tabLabel, tabIndex} { - e.stopPropagation(); - clickAction?.(tabIndex); - }} + on:pointerdown={(e) => tabPointerDown(e, tabIndex)} + on:click={(e) => e.stopPropagation()} on:auxclick={(e) => { // Middle mouse button click if (e.button === BUTTON_MIDDLE) { @@ -90,11 +233,15 @@ }} icon="CloseX" size={16} + data-close-button /> {/if} {/each} + {#if dragging && insertionMarkerLeft !== undefined} +
+ {/if}
{#if panelType} @@ -110,6 +257,7 @@ overflow: hidden; .tab-bar { + position: relative; height: 28px; min-height: auto; background: var(--color-1-nearblack); // Needed for the viewport hole punch on desktop @@ -217,6 +365,21 @@ } } } + + &:has(.tab-insertion-mark) .tab .icon-button { + pointer-events: none; + } + + .tab-insertion-mark { + position: absolute; + top: 4px; + bottom: 4px; + width: 3px; + margin-left: -2px; + z-index: 1; + background: var(--color-e-nearwhite); + pointer-events: none; + } } .panel-body { diff --git a/frontend/src/components/window/Workspace.svelte b/frontend/src/components/window/Workspace.svelte index b852382e8a..472574ad81 100644 --- a/frontend/src/components/window/Workspace.svelte +++ b/frontend/src/components/window/Workspace.svelte @@ -151,6 +151,7 @@ emptySpaceAction={() => editor.newDocumentDialog()} clickAction={(tabIndex) => editor.selectDocument($portfolio.documents[tabIndex].id)} closeAction={(tabIndex) => editor.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)} + reorderAction={(oldIndex, newIndex) => editor.reorderDocument($portfolio.documents[oldIndex].id, newIndex)} tabActiveIndex={$portfolio.activeDocumentIndex} bind:this={documentPanel} /> diff --git a/frontend/src/stores/portfolio.ts b/frontend/src/stores/portfolio.ts index 0f482736ae..5651b3309e 100644 --- a/frontend/src/stores/portfolio.ts +++ b/frontend/src/stores/portfolio.ts @@ -2,6 +2,7 @@ import { writable } from "svelte/store"; import type { Writable } from "svelte/store"; import type { SubscriptionsRouter } from "/src/subscriptions-router"; import { downloadFile, downloadFileBlob, upload } from "/src/utility-functions/files"; +import { storeDocumentTabOrder } from "/src/utility-functions/persistence"; import { rasterizeSVG } from "/src/utility-functions/rasterization"; import type { EditorWrapper, OpenDocument } from "/wrapper/pkg/graphite_wasm_wrapper"; @@ -41,6 +42,7 @@ export function createPortfolioStore(subscriptions: SubscriptionsRouter, editor: state.documents = data.openDocuments; return state; }); + storeDocumentTabOrder({ subscribe }); }); subscriptions.subscribeFrontendMessage("UpdateActiveDocument", (data) => { diff --git a/frontend/src/utility-functions/persistence.ts b/frontend/src/utility-functions/persistence.ts index 15156afbb3..3ef31dadc3 100644 --- a/frontend/src/utility-functions/persistence.ts +++ b/frontend/src/utility-functions/persistence.ts @@ -6,6 +6,11 @@ import type { EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper"; const PERSISTENCE_DB = "graphite"; const PERSISTENCE_STORE = "store"; +export async function storeDocumentTabOrder(portfolio: PortfolioStore) { + const documentOrder = get(portfolio).documents.map((doc) => String(doc.id)); + await databaseSet("documents_tab_order", documentOrder); +} + export async function storeCurrentDocumentId(documentId: string) { await databaseSet("current_document_id", String(documentId)); } @@ -17,8 +22,7 @@ export async function storeDocument(autoSaveDocument: MessageBody<"TriggerPersis return documents; }); - const documentOrder = get(portfolio).documents.map((doc) => String(doc.id)); - await databaseSet("documents_tab_order", documentOrder); + await storeDocumentTabOrder(portfolio); await storeCurrentDocumentId(String(autoSaveDocument.documentId)); } diff --git a/frontend/wrapper/src/editor_wrapper.rs b/frontend/wrapper/src/editor_wrapper.rs index 13fdca90de..54a048ce22 100644 --- a/frontend/wrapper/src/editor_wrapper.rs +++ b/frontend/wrapper/src/editor_wrapper.rs @@ -427,6 +427,13 @@ impl EditorWrapper { self.dispatch(message); } + #[wasm_bindgen(js_name = reorderDocument)] + pub fn reorder_document(&self, document_id: u64, new_index: usize) { + let document_id = DocumentId(document_id); + let message = PortfolioMessage::ReorderDocument { document_id, new_index }; + self.dispatch(message); + } + #[wasm_bindgen(js_name = closeDocumentWithConfirmation)] pub fn close_document_with_confirmation(&self, document_id: u64) { let document_id = DocumentId(document_id);