@setoelkahfi / svara / commits / 52ad9b7

rewritten tab logic to handle multiple instances of tablists

mellbacon committed May 15, 2023 at 13:29 UTC 52ad9b71603d0b252e7cce71ee46360bdc83aeaa
6 files changed +197 -171
src/lib/EditorTabList.svelte
+38 -41
@@ -1,44 +1,51 @@
1 <script lang="ts">
2 - import { afterUpdate, onMount } from "svelte";
3 - import Tab from "./Tab.svelte";
4 - import { tabs, hidden, CloseAllTabs } from "./scripts/Tab";
5 - import Sortable from 'sortablejs';
2 import VerticalDots from "carbon-icons-svelte/lib/OverflowMenuVertical.svelte";
3 import Dropdown from "./utility/Dropdown.svelte";
8 - import { showsidebarview } from "./Sidebar.svelte";
9 - import { getThemeProperty } from "../config/themehandler";
4 + import TabList from "./Tab/TabList.svelte";
5 + import { Tab } from "./Tab/Tab";
6 +</script>
7 +<script lang="ts" context="module">
8 + class EditorTab {
9 + id: number;
10 + label: string;
11 + active: boolean;
12 + path: string;
13 + content;
14 + isfile: boolean;
15 + saved = true;
16 + constructor(id: number, label = "", content = null, path = "") {
17 + this.id = id;
18 + this.label = label === "" ? `Untitled-${id}` : label;
19 + this.path = path === "" ? this.label : path;
20 + this.content = content;
21 + }
22 + updateView(id) {
23 + this.content.$set({ hidden: !(this.id === id) });
24 + }
25 + refreshView(tab) {
26 + if (tab.isfile) {
27 + tab.content.updateTheme();
28 + }
29 + }
30 + }
31
11 - let tabcontainer = null;
32 + const editorTab = new Tab(EditorTab);
33
13 - onMount(() => {
14 - Sortable.create(tabcontainer, {
15 - draggable: ".tab",
16 - animation: 150,
17 - forceFallback: true,
18 - easing: "cubic-bezier(1, 0, 0, 1)",
19 - sort: true
20 - })
21 - })
34 + export function addTab(path?: string, label?: string) {
35 + editorTab.addTab(path, label);
36 + }
37 + export function closeTab(tabid: number) {
38 + editorTab.closeTab(tabid);
39 + }
40
23 - afterUpdate(() => {
24 - // css trick to take care of the tabs overlapping the sidebar border for some reason
25 - if (!$showsidebarview) {
26 - tabcontainer.style.borderLeft = `1px solid ${getThemeProperty("window-borderColor")}`;
27 - }
28 - else {
29 - tabcontainer.style.borderLeft = "";
30 - }
31 - })
41 + let hidden = editorTab.hidden;
42 + let tabs = editorTab.tabs;
43 </script>
44 <div id="editor-tabs" class:hidden={$hidden}>
34 - <div bind:this={tabcontainer} id="editor-tablist">
35 - {#each $tabs as tab}
36 - <Tab id={tab.id} label={tab.label} path={tab.path} active={tab.active} />
37 - {/each}
38 - </div>
45 + <TabList tabs={tabs} on:closetab={(e) => {closeTab(e.detail.tabid)}} on:select={(e) => {editorTab.setActive(e.detail.tabid)}}></TabList>
46 <div class="tab-toolbar">
47 <Dropdown right menu={{icon: VerticalDots, children: [
41 - {name: "Close All Tabs", action: () => {CloseAllTabs()}},
48 + {name: "Close All Tabs", action: () => {editorTab.CloseAllTabs()}},
49 {name: "Close Saved Tabs", disabled: true}
50 ]}}></Dropdown>
51 </div>
@@ -53,16 +60,6 @@
60 display: flex;
61 justify-content: space-between;
62 position: absolute;
56 - #editor-tablist {
57 - display: flex;
58 - overflow-x: overlay;
59 - &::-webkit-scrollbar {
60 - height: 5px;
61 - }
62 - &::-webkit-scrollbar-thumb {
63 - background-color: #e8e8e81f;
64 - }
65 - }
63 }
64 .tab-toolbar {
65 height: 100%;
src/lib/Header.svelte
+1 -1
@@ -2,7 +2,7 @@
2 import { appWindow } from "@tauri-apps/api/window";
3 import Dropdown from "./utility/Dropdown.svelte";
4 import Settings from "carbon-icons-svelte/lib/Settings.svelte";
5 - import { addTab } from "./scripts/Tab";
5 + import { addTab } from "../lib/EditorTabList.svelte";
6
7 const items = [
8 {menuname: "File", children: [
src/lib/Tab/Tab.svelte renamed
+10 -23
@@ -1,6 +1,6 @@
1 <script lang="ts">
2 - import { setActive, closeTab } from "./scripts/Tab";
3 - import ContextMenu from "./utility/ContextMenu.svelte";
2 + import { createEventDispatcher } from "svelte";
3 + const dispatch = createEventDispatcher();
4
5 export let id = 0;
6 export let label = "Untitled-1";
@@ -9,41 +9,28 @@
9
10 let tab = null;
11
12 - let contextmenu = false;
13 - let contextmenuitems = [
14 - {name: "Close Tab", shortcut: "AHHH", action: () => {console.log("click")}},
15 - {name: "Close Others", shortcut: "Ctrl + C", action: () => {console.log("click")}},
16 - {name: "Close All Right", shortcut: "Ctrl + X", action: () => {console.log("click")}},
17 - {name: "Close All Left", shortcut: "AHHH", action: () => {console.log("click")}},
18 - {name: "Close All Saved", shortcut: "AHHH", action: () => {console.log("click")}},
19 - {name: "Close All", shortcut: "AHHH", action: () => {console.log("click")}},
20 - {name: "Copy Filename", shortcut: "F2", disabled: true, action: () => {console.log("click")}},
21 - {name: "Copy Absolute Path", shortcut: "F2", disabled: true, action: () => {console.log("click")}},
22 - {name: "Show In File Explorer", shortcut: "Delete", action: () => {console.log("click")}}
23 - ]
12 + function handleSelect(tabid) {
13 + dispatch("select", {tabid: tabid})
14 + }
15 + function handleClose(tabid) {
16 + dispatch("closetab", {tabid: tabid});
17 + }
18 </script>
19 <div bind:this={tab} title={path} id={`editorTab-${id}`} class="tab" class:active={active}>
20 <!-- svelte-ignore a11y-click-events-have-key-events -->
21 <div class="tab-content" on:mousedown = {
22 (e) => {
29 - if (e.button === 0) setActive(id);
30 - if (e.button === 2) contextmenu = true;
23 + if (e.button === 0) handleSelect(id);
24 }
25 }>
26 <span class="tab-label">{label}</span>
27 </div>
28 <!-- svelte-ignore a11y-click-events-have-key-events -->
29 <div class="close-tab">
37 - <span title="Close tab" on:click={() => {closeTab(id)}}></span>
30 + <span title="Close tab" on:click={() => {handleClose(id)}}></span>
31 </div>
32 </div>
33
41 -{#if contextmenu}
42 -<!-- TODO: Fix menu position somehow
43 - <ContextMenu target={tab} items={contextmenuitems}></ContextMenu>
44 --->
45 -{/if}
46 -
34 <style lang="scss">
35 .tab {
36 height: 100%;
src/lib/Tab/Tab.ts new
+91
@@ -0,0 +1,91 @@
1 +import { writable } from "svelte/store";
2 +
3 +export class Tab {
4 + id = 0;
5 + activeid = this.id;
6 + tablist = [];
7 + Tab; // Tab class
8 + hidden = writable(true);
9 + tabs = writable([]);
10 + constructor (Tab) {
11 + this.Tab = Tab;
12 + }
13 +
14 + setActive(id: number) {
15 + for (let tab of this.tablist) {
16 + if (tab.id === id) {
17 + this.activeid = id;
18 + tab.active = true;
19 + }
20 + else {
21 + tab.active = false;
22 + }
23 + }
24 + this.tabs.set(this.tablist);
25 + }
26 + updateTabs() {
27 + if (this.tablist.length === 0) {
28 + this.hidden.set(true);
29 + this.id = 0;
30 + return;
31 + }
32 +
33 + if (this.tablist.length > 0) {
34 + this.hidden.set(false);
35 + }
36 +
37 + this.tabs.set(this.tablist);
38 + this.setActive(this.id);
39 + this.id++;
40 + }
41 + tabOpen(path: string) {
42 + for (const tab of this.tablist) {
43 + if (tab.path === path) {
44 + this.setActive(tab.id);
45 + return true;
46 + }
47 + }
48 + return false;
49 + }
50 + addTab(path: string = "", label: string = "") {
51 + // dont add tabs that are already open
52 + if (this.tabOpen(path)) {
53 + return;
54 + }
55 +
56 + let tab = new this.Tab(this.id, label, null, path);
57 + this.tablist = [...this.tablist, tab];
58 +
59 + this.updateTabs();
60 + }
61 + closeTab(tabid: number) {
62 + if (this.activeid === tabid) {
63 + for (let i = 0; i <= this.tablist.length - 1; i++) {
64 + // set right tab active
65 + if (this.tablist[i].id === tabid && this.tablist[i + 1]) {
66 + this.setActive(this.tablist[i + 1].id);
67 + break;
68 + }
69 + // set left tab active
70 + else if (this.tablist[i].id === tabid && this.tablist[i - 1]) {
71 + this.setActive(this.tablist[i - 1].id);
72 + break;
73 + }
74 + }
75 + }
76 +
77 + //tablist.find(t => t.id === tabid).content.$destroy();
78 + this.tablist = this.tablist.filter(t => t.id !== tabid);
79 + this.tabs.set(this.tablist);
80 +
81 + if (this.tablist.length === 0) {
82 + this.hidden.set(true);
83 + this.id = 0;
84 + }
85 + }
86 + CloseAllTabs() {
87 + for (const tab of this.tablist) {
88 + this.closeTab(tab.id);
89 + }
90 + }
91 +}
src/lib/Tab/TabList.svelte new
+57
@@ -0,0 +1,57 @@
1 +<script lang="ts">
2 + import { afterUpdate, onMount } from "svelte";
3 + import Tab from "./Tab.svelte";
4 + import Sortable from 'sortablejs';
5 + import { showsidebarview } from "../Sidebar.svelte";
6 + import { getThemeProperty } from "../../config/themehandler";
7 + import { type Writable } from "svelte/store";
8 +
9 + export let tabs: Writable<any[]>;
10 + export let addTab = false;
11 +
12 + let tabcontainer = null;
13 +
14 + onMount(() => {
15 + Sortable.create(tabcontainer, {
16 + draggable: ".tab",
17 + animation: 150,
18 + forceFallback: true,
19 + easing: "cubic-bezier(1, 0, 0, 1)",
20 + sort: true
21 + })
22 + })
23 +
24 + afterUpdate(() => {
25 + // css trick to take care of the tabs overlapping the sidebar border for some reason
26 + if (!$showsidebarview) {
27 + tabcontainer.style.borderLeft = `1px solid ${getThemeProperty("window-borderColor")}`;
28 + }
29 + else {
30 + tabcontainer.style.borderLeft = "";
31 + }
32 +
33 + if (addTab) {
34 + console.log("will add tab")
35 + addTab = false;
36 + }
37 + })
38 +</script>
39 +<div bind:this={tabcontainer} id="tablist">
40 + {#each $tabs as tab}
41 + <Tab on:closetab on:select id={tab.id} label={tab.label} path={tab.path} active={tab.active} />
42 + {/each}
43 +</div>
44 +
45 +<style lang="scss">
46 + #tablist {
47 + position: relative;
48 + display: flex;
49 + overflow-x: overlay;
50 + &::-webkit-scrollbar {
51 + height: 5px;
52 + }
53 + &::-webkit-scrollbar-thumb {
54 + background-color: #e8e8e81f;
55 + }
56 + }
57 +</style>
\ No newline at end of file
src/lib/scripts/Tab.ts deleted
-106
@@ -1,106 +0,0 @@
1 -import { writable } from "svelte/store";
2 -
3 -export let hidden = writable(true);
4 -export let tablist: Tab[] = [];
5 -export let tabs = writable([]);
6 -
7 -let id = 0;
8 -let activeid = id;
9 -class Tab {
10 - id: number;
11 - label: string;
12 - active: boolean;
13 - path: string;
14 - content;
15 - isfile: boolean;
16 - saved = true;
17 - constructor(id: number, label = "", content = null, path = "") {
18 - this.id = id;
19 - this.label = label === "" ? `Untitled-${id}` : label;
20 - this.path = path === "" ? this.label : path;
21 - this.content = content;
22 - }
23 - updateView(id) {
24 - this.content.$set({ hidden: !(this.id === id) });
25 - }
26 - refreshView(tab) {
27 - if (tab.isfile) {
28 - tab.content.updateTheme();
29 - }
30 - }
31 -}
32 -
33 -export function addTab(path: string = "", label: string = "") {
34 - // dont add tabs that are already open
35 - if (tabOpen(path)) {
36 - return;
37 - }
38 -
39 - let tab = new Tab(id, label, null, path);
40 - tablist = [...tablist, tab];
41 -
42 - refreshTabs();
43 -}
44 -
45 -export function setActive(id: number) {
46 - for (let tab of tablist) {
47 - if (tab.id === id) {
48 - activeid = id;
49 - tab.active = true;
50 - }
51 - else {
52 - tab.active = false;
53 - }
54 - }
55 - tabs.set(tablist);
56 -}
57 -
58 -export function CloseAllTabs() {
59 - for (const tab of tablist) {
60 - closeTab(tab.id);
61 - }
62 -}
63 -
64 -export function closeTab(tabid: number) {
65 - if (activeid === tabid) {
66 - for (let i = 0; i <= tablist.length - 1; i++) {
67 - // set right tab active
68 - if (tablist[i].id === tabid && tablist[i + 1]) {
69 - setActive(tablist[i + 1].id);
70 - break;
71 - }
72 - // set left tab active
73 - else if (tablist[i].id === tabid && tablist[i - 1]) {
74 - setActive(tablist[i - 1].id);
75 - break;
76 - }
77 - }
78 - }
79 -
80 - //tablist.find(t => t.id === tabid).content.$destroy();
81 - tablist = tablist.filter(t => t.id !== tabid);
82 - tabs.set(tablist);
83 -
84 - if (tablist.length === 0) {
85 - hidden.set(true);
86 - id = 0;
87 - }
88 -}
89 -
90 -function refreshTabs() {
91 - if (tablist.length > 0) {
92 - hidden.set(false);
93 - }
94 - tabs.set(tablist);
95 - setActive(id);
96 - id++;
97 -}
98 -function tabOpen(path: string) {
99 - for (const tab of tablist) {
100 - if (tab.path === path) {
101 - setActive(tab.id);
102 - return true;
103 - }
104 - }
105 - return false;
106 -}
\ No newline at end of file