Skip to content

Commit

Permalink
improved tab data handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ivicic-petr committed Nov 7, 2023
1 parent 5971558 commit dd105c3
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 121 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@tauri-apps/api": "^1.4.0",
"dataclass": "^2.1.1",
"lit": "^3.0.2",
"uikit": "^3.17.8"
},
Expand Down
35 changes: 14 additions & 21 deletions src/html/component/content-pane/content-pane.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,36 @@
import { css, html, LitElement, type TemplateResult, unsafeCSS } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { customElement, property } from 'lit/decorators.js'
import style_less from './content-pane.less?inline'
import { type TabData } from '../../util/tab-data'
import UIkit from 'uikit'
import Icons from 'uikit/dist/js/uikit-icons'

UIkit.use(Icons)

@customElement('content-pane')
export class ContentPane extends LitElement {
static styles = css`${unsafeCSS(style_less)}`

private _tabId = -1
@state() private isPinned = false
@state() private content = ''

constructor () {
super()
this.addEventListener('switch-tab', (e) => {
this._tabId = (e as CustomEvent).detail.tabId
this.content = `Content of tab ${this._tabId}`
})
}
@property() declare private readonly tab: TabData

private pin (): void {
this.isPinned = !this.isPinned
this.dispatchEvent(new CustomEvent(this.isPinned ? 'pin-pane' : 'unpin-pane', {
this.dispatchEvent(new CustomEvent(this.tab.pinned ? 'unpin-tab' : 'pin-tab', {
detail: {
tabId: this._tabId
tabId: this.tab.id
},
bubbles: true,
composed: true
}))
}

get tabId (): number {
return this._tabId
}

protected render (): TemplateResult {
return html`
<div class="content-pane uk-container uk-container-expand uk-margin-top">
<button class="uk-button uk-button-small uk-button-secondary pin-button" @click="${this.pin}">${this.isPinned ? 'unpin' : 'pin'}</button>
<h1 class="uk-heading-large uk-text-success">${this.content}</h1>
<button class="uk-button uk-button-small uk-button-secondary pin-button" @click="${this.pin}">${this.tab.pinned ? 'unpin' : 'pin'}</button>
<span class="uk-margin-small-right uk-icon" uk-icon="lock"></span>
<span uk-icon="icon: home; ratio: 2"></span>
<span uk-icon="icon: settings; ratio: 2"></span>
<h1 class="uk-heading-large uk-text-success">${this.tab.data}</h1>
</div>
`
}
Expand Down
5 changes: 5 additions & 0 deletions src/html/component/nav-bar/nav-bar.less
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
@import "uikit/src/less/uikit.theme.less";

.nav-bar {
overflow: hidden;
min-width: 20vw;
}
6 changes: 4 additions & 2 deletions src/html/component/nav-bar/nav-bar.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { html, css, unsafeCSS, LitElement, type TemplateResult } from 'lit'
import { customElement } from 'lit/decorators.js'
import { customElement, property } from 'lit/decorators.js'
import style_less from './nav-bar.less?inline'
import '../menu/menu'
import '../tab-bar/tab-bar'
import '../undo-redo/undo-redo'
import '../search/search'
import { type TabData } from '../../util/tab-data'

@customElement('nav-bar')
class NavBar extends LitElement {
static styles = css`${unsafeCSS(style_less)}`
@property() tabs: TabData[] = []

render (): TemplateResult {
return html`
Expand All @@ -17,7 +19,7 @@ class NavBar extends LitElement {
<div class="uk-navbar uk-margin-small-top uk-flex-nowrap">
<div class="uk-navbar-left uk-flex-nowrap">
<hamburger-menu></hamburger-menu>
<tab-bar></tab-bar>
<tab-bar .tabs=${this.tabs}></tab-bar>
</div>
<div class="uk-navbar-right uk-flex-nowrap">
Expand Down
92 changes: 74 additions & 18 deletions src/html/component/root-component/root-component.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,101 @@
import { html, css, unsafeCSS, LitElement, type TemplateResult } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { map } from 'lit/directives/map.js'
import style_less from './root-component.less?inline'
import '../content-pane/content-pane'
import '../nav-bar/nav-bar'
import { type ContentPane } from '../content-pane/content-pane'
import { TabData } from '../../util/tab-data'

const PIN_LIMIT = 1000
const SAVE_KEY = 'tab-data'

@customElement('root-component')
class RootComponent extends LitElement {
static styles = css`${unsafeCSS(style_less)}`
@state() panes: ContentPane[] = []
@state() tabs: TabData[] = []
currentIndex = 0
constructor () {
super()
this.loadTabs()
this.addEventListener('switch-tab', this.switchTab)
this.addEventListener('pin-pane', this.pinPane)
this.addEventListener('pin-tab', this.pinTab)
this.addEventListener('unpin-tab', this.pinTab)
this.addEventListener('add-tab', this.addTab)
this.addEventListener('reset', this.reset)
}

private pinPane (): void {
console.log('pin')
this.panes[this.panes[PIN_LIMIT].tabId] = this.panes[PIN_LIMIT]
private addTab (): void {
this.currentIndex++
this.tabs = this.tabs.concat(TabData.create({
id: this.currentIndex,
name: `Tab ${this.currentIndex}`,
data: `Contents of tab ${this.currentIndex}`
}))
this.saveTabs()
}

private saveTabs (): void {
const tabData = this.tabs.map((tab) => ({
data: tab.data,
name: tab.name,
id: tab.id,
active: tab.active
}))
localStorage.setItem(SAVE_KEY, JSON.stringify(tabData))
}

private loadTabs (): void {
const tabData = JSON.parse(localStorage.getItem(SAVE_KEY) ?? '[]')
this.tabs = tabData.map((data: { id: number, name: string, data: string, active: boolean }) => {
this.currentIndex = Math.max(this.currentIndex, +data.id)
return TabData.create({
id: data.id,
name: data.name,
data: data.data,
active: data.active
})
})
console.log(this.tabs)
}

private pinTab (e: Event): void {
const tabId = (e as CustomEvent).detail.tabId
if (tabId === undefined) return
const tabIndex = this.tabs.findIndex((tab) => tab.id === tabId)
if (tabIndex === -1) return
this.tabs[tabIndex] = this.tabs[tabIndex].copy({ pinned: e.type === 'pin-tab' })
console.log('tab pin:', this.tabs[tabIndex], e.type)
this.requestUpdate()
this.panes[PIN_LIMIT] = document.createElement('content-pane') as ContentPane
this.saveTabs()
}

private switchTab (e: Event): void {
this.panes[PIN_LIMIT].dispatchEvent(new CustomEvent('switch-tab', {
detail: {
tabId: (e as CustomEvent).detail.tabId
}
}))
const tabId = (e as CustomEvent).detail.tabId
if (tabId === undefined) return
this.tabs = this.tabs.map((tab) =>
tab.copy({
active: tab.id === tabId
})
)
this.requestUpdate()
this.saveTabs()
}

render (): TemplateResult {
this.panes[PIN_LIMIT] = document.createElement('content-pane') as ContentPane
private reset (): void {
this.currentIndex = 0
this.tabs = []
this.addTab()
localStorage.removeItem(SAVE_KEY)
}

render (): TemplateResult {
return html`
<div class="uk-container-expand">
<nav-bar></nav-bar>
<div></div>
${this.panes}
<nav-bar .tabs=${this.tabs}></nav-bar>
<div class="uk-flex uk-flex-row">
${map(this.tabs.sort((a, b) => a.id - b.id).filter((tab) => tab.pinned || tab.active), (tab) => html`
<content-pane .tab=${tab}></content-pane>
`)}
</div>
</div>
`
}
Expand Down
77 changes: 18 additions & 59 deletions src/html/component/tab-bar/tab-bar.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,48 @@
import { html, css, LitElement, type TemplateResult, unsafeCSS } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { customElement, property } from 'lit/decorators.js'
import { map } from 'lit/directives/map.js'
import style_less from './tab-bar.less?inline'

const SAVE_KEY = 'tabs'
import { type TabData } from '../../util/tab-data'

@customElement('tab-bar')
class TabBar extends LitElement {
static styles = css`${unsafeCSS(style_less)}`

@state() private tabs: HTMLButtonElement[] = []
private currentIndex = 0

constructor () {
super()
this.loadTabs()
if (this.tabs.length === 0) {
this.reset()
}
}
@property() tabs: TabData[] = []

private addTab (): void {
this.currentIndex++
this.createTab(this.currentIndex, `Tab ${this.currentIndex}`, ['tab', 'uk-button', 'uk-button-secondary'])
console.log(`tab ${this.currentIndex} added`)
}

private saveTabs (): void {
const tabData = this.tabs.map((tab) => ({
classList: Array.from(tab.classList),
textContent: tab.textContent,
index: tab.dataset.index
}))
localStorage.setItem(SAVE_KEY, JSON.stringify(tabData))
}

private loadTabs (): void {
const tabData = JSON.parse(localStorage.getItem(SAVE_KEY) ?? '[]')
tabData.forEach((data: { classList: string[], textContent: string, index: string }) => {
this.createTab(+data.index, data.textContent, data.classList)
this.currentIndex = Math.max(this.currentIndex, +data.index)
})
}

private createTab (index: number, title: string, classList: string[]): void {
const newTabButton = document.createElement('button')
newTabButton.classList.add(...classList)
newTabButton.onclick = this.switchTab(newTabButton)
newTabButton.dataset.index = index.toString()
newTabButton.textContent = title
this.tabs = this.tabs.concat(newTabButton)

this.saveTabs()
this.dispatchEvent(new Event('add-tab', { bubbles: true, composed: true }))
this.requestUpdate()
}

switchTab (newTabButton: HTMLButtonElement) {
switchTab (tabId: number) {
return () => {
this.tabs.forEach((tab) => {
tab.classList.remove('uk-button-primary')
tab.classList.add('uk-button-secondary')
})
newTabButton.classList.remove('uk-button-secondary')
newTabButton.classList.add('uk-button-primary')
const currentIndex = +(newTabButton.dataset.index ?? 0)
this.dispatchEvent(new CustomEvent('switch-tab', {
detail: {
tabId: currentIndex
tabId
},
bubbles: true,
composed: true
}))
this.requestUpdate()
}
}

private reset (): void {
this.currentIndex = 0
this.tabs.forEach(tab => { tab.remove() })
this.tabs = []
this.addTab()
localStorage.removeItem(SAVE_KEY)
this.dispatchEvent(new Event('reset', { bubbles: true, composed: true }))
this.requestUpdate()
}

render (): TemplateResult {
return html`
<div class="tabs">
${this.tabs}
${map(this.tabs, (tab) => html`
<button class="tab uk-button ${tab.active ? 'uk-button-primary' : 'uk-button-secondary'}"
@click=${this.switchTab(tab.id)}
${tab.pinned ? 'disabled' : ''}>
${tab.name}
</button>
`)}
<button class="uk-button uk-button-default uk-button-small new-tab-button" @click=${this.addTab}><span class="plus-symbol">+</span></button>
<button class="uk-button-small uk-button-secondary uk-margin-left" @click=${this.reset}>\u21bb</button>
</div>
Expand Down
8 changes: 7 additions & 1 deletion src/html/component/undo-redo/undo-redo.less
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
@import "uikit/src/less/uikit.theme.less";
@import "uikit/src/less/uikit.theme.less";

.undo-redo {
min-width: 5em;
width: 100%;
overflow: hidden;
}
2 changes: 1 addition & 1 deletion src/html/component/undo-redo/undo-redo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class UndoRedo extends LitElement {

render (): TemplateResult {
return html`
<div class="uk-flex-nowrap">
<div class="undo-redo uk-flex-nowrap">
<button class="uk-button uk-button-secondary uk-button-small">&#8249;</button>
<button class="uk-button uk-button-secondary uk-button-small" disabled>&#8250;</button>
</div>
Expand Down
9 changes: 9 additions & 0 deletions src/html/util/tab-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Data } from 'dataclass'

export class TabData extends Data {
id: number = -1
name: string = 'unknown'
pinned: boolean = false
data: string = 'unknown'
active: boolean = false
}
Loading

0 comments on commit dd105c3

Please sign in to comment.