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