@eclipse-docks/extension-dataviewer 0.7.122 → 0.7.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -215,14 +215,14 @@ var DataViewPart = class DataViewPart extends DocksPart {
215
215
  <wa-dropdown
216
216
  placement="bottom-start"
217
217
  distance="4"
218
- size="small"
218
+ size="s"
219
219
  hoist
220
220
  @wa-select=${(e) => this.onHistorySelect(e)}
221
221
  >
222
222
  <wa-button
223
223
  slot="trigger"
224
224
  appearance="plain"
225
- size="small"
225
+ size="s"
226
226
  with-caret
227
227
  title="Data view history"
228
228
  >
@@ -235,7 +235,7 @@ var DataViewPart = class DataViewPart extends DocksPart {
235
235
  <wa-button
236
236
  slot="details"
237
237
  appearance="plain"
238
- size="small"
238
+ size="s"
239
239
  title="Clear history"
240
240
  @click=${() => this.onClearHistory()}
241
241
  >
@@ -253,7 +253,7 @@ var DataViewPart = class DataViewPart extends DocksPart {
253
253
  <wa-button
254
254
  slot="details"
255
255
  appearance="plain"
256
- size="small"
256
+ size="s"
257
257
  title="Delete data view"
258
258
  @click=${(e) => this.onDeleteView(e, entry.storageKey)}
259
259
  >
@@ -267,7 +267,7 @@ var DataViewPart = class DataViewPart extends DocksPart {
267
267
  <wa-divider orientation="vertical"></wa-divider>
268
268
 
269
269
  <wa-button
270
- size="small"
270
+ size="s"
271
271
  appearance="plain"
272
272
  title="Export current data view to CSV"
273
273
  ?disabled=${!this.hasData}
@@ -278,7 +278,7 @@ var DataViewPart = class DataViewPart extends DocksPart {
278
278
 
279
279
  <wa-switch
280
280
  ?checked=${this.autoActivateTab}
281
- size="small"
281
+ size="s"
282
282
  title="Switch to this tab when new results arrive"
283
283
  @change=${(e) => this.onAutoActivateChange(e)}
284
284
  style="margin-top: 0.5rem;"
@@ -377,4 +377,4 @@ function dataviewer_extension_default() {}
377
377
  //#endregion
378
378
  export { dataviewer_extension_default as default };
379
379
 
380
- //# sourceMappingURL=dataviewer-extension-BW9umf3B.js.map
380
+ //# sourceMappingURL=dataviewer-extension-DhE3y4Rw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataviewer-extension-DhE3y4Rw.js","names":[],"sources":["../src/dataviewer-service.ts","../src/dataview-part.ts","../src/parse-csv.ts","../src/dataviewer-extension.ts"],"sourcesContent":["import { persistenceService, subscribe, publish } from '@eclipse-docks/core';\nimport { v4 } from '@eclipse-docks/core/externals/third-party';\nimport type { DataView } from './api';\nimport { TOPIC_DATAVIEW_PUBLISH, TOPIC_DATAVIEW_ADDED } from './api';\n\nconst KEY_PREFIX = 'dataview/';\nconst KEY_INDEX = KEY_PREFIX + 'index';\n\nexport interface DataviewListEntry {\n storageKey: string;\n title: string;\n source?: string;\n createdAt: number;\n}\n\ntype IndexEntry = DataviewListEntry;\n\nexport class DataviewerService {\n private subscriptionToken?: string;\n\n init(): void {\n if (this.subscriptionToken !== undefined) return;\n this.subscriptionToken = subscribe(TOPIC_DATAVIEW_PUBLISH, (payload: DataView) => {\n void this.handlePublish(payload);\n });\n }\n\n private async handlePublish(payload: DataView): Promise<void> {\n const storageKey = v4();\n const createdAt = Date.now();\n const entry: DataView = {\n id: payload.id ?? storageKey,\n title: payload.title,\n data: payload.data,\n source: payload.source,\n createdAt,\n };\n await persistenceService.persistObject(KEY_PREFIX + storageKey, entry);\n const index = (await persistenceService.getObject(KEY_INDEX)) as IndexEntry[] | undefined;\n const list = Array.isArray(index) ? index : [];\n list.push({ storageKey, title: payload.title, source: payload.source, createdAt });\n await persistenceService.persistObject(KEY_INDEX, list);\n publish(TOPIC_DATAVIEW_ADDED, { storageKey, title: payload.title, createdAt });\n }\n\n async listViews(): Promise<DataviewListEntry[]> {\n const raw = await persistenceService.getObject(KEY_INDEX);\n if (!Array.isArray(raw) || raw.length === 0) return [];\n if (typeof raw[0] === 'string') {\n return (raw as string[]).map((storageKey) => ({\n storageKey,\n title: storageKey,\n createdAt: 0,\n }));\n }\n const list = raw as IndexEntry[];\n return [...list].sort((a, b) => a.createdAt - b.createdAt);\n }\n\n async getView(storageKey: string): Promise<DataView | null> {\n const entry = await persistenceService.getObject(KEY_PREFIX + storageKey);\n return entry ?? null;\n }\n\n async deleteView(storageKey: string): Promise<void> {\n const index = (await persistenceService.getObject(KEY_INDEX)) as IndexEntry[] | undefined;\n const list = Array.isArray(index) ? index.filter((e) => e.storageKey !== storageKey) : [];\n await persistenceService.persistObject(KEY_INDEX, list);\n await persistenceService.persistObject(KEY_PREFIX + storageKey, null);\n }\n\n async clearAllViews(): Promise<void> {\n const index = (await persistenceService.getObject(KEY_INDEX)) as IndexEntry[] | undefined;\n const list = Array.isArray(index) ? index : [];\n await Promise.all(list.map((entry) => persistenceService.persistObject(KEY_PREFIX + entry.storageKey, null)));\n await persistenceService.persistObject(KEY_INDEX, []);\n }\n}\n\nexport const dataviewerService = new DataviewerService();\n","import { customElement, property, state } from 'lit/decorators.js';\nimport { css, html } from 'lit';\nimport { DocksPart, toastError, filebrowserDialog } from '@eclipse-docks/core';\nimport { dataviewerService, type DataviewListEntry } from './dataviewer-service';\nimport type { DataView } from './api';\nimport { TOPIC_DATAVIEW_ADDED } from './api';\nimport { when } from '@eclipse-docks/core/externals/lit';\nimport './docks-data-table';\n\n@customElement('docks-dataview')\nexport class DataViewPart extends DocksPart {\n @property({ attribute: false })\n dataview: DataView | null = null;\n\n @state()\n private persistedList: DataviewListEntry[] = [];\n\n @state()\n private selectedStorageKey = '';\n\n @state()\n private selectedView: DataView | null = null;\n\n @state()\n private loadingList = true;\n\n @state()\n private autoActivateTab = true;\n\n private get displayed(): DataView | null {\n return this.selectedView ?? this.dataview;\n }\n\n private get hasData(): boolean {\n const dv = this.displayed;\n if (!dv) return false;\n const { columns, rows } = dv.data;\n return Array.isArray(columns) && Array.isArray(rows) && (columns.length > 0 || rows.length > 0);\n }\n\n private toCsv(dv: DataView): string {\n const { columns, rows } = dv.data;\n const escapeCell = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n const str = String(value);\n if (!/[\",\\n]/.test(str)) return str;\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n };\n const header = columns.map(escapeCell).join(',');\n const body = rows.map((row) => row.map(escapeCell).join(',')).join('\\n');\n return body ? `${header}\\n${body}` : header;\n }\n\n private async onExportCsv(): Promise<void> {\n const dv = this.displayed;\n if (!dv || !this.hasData) return;\n try {\n const csv = this.toCsv(dv);\n const safeTitle = dv.title?.trim() || 'dataview';\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n const fileName = `${safeTitle.replace(/[^a-zA-Z0-9-_]+/g, '_')}-${timestamp}.csv`;\n const targetDir = await filebrowserDialog('directory');\n if (!targetDir) return;\n await this.executeCommand('touch', {\n path: `${targetDir}/${fileName}`,\n contents: csv,\n });\n } catch (err) {\n toastError(err instanceof Error ? err.message : String(err));\n }\n }\n\n protected async doInitUI() {\n const persisted = await this.getDialogSetting();\n if (persisted && typeof persisted.autoActivateTab === 'boolean') this.autoActivateTab = persisted.autoActivateTab;\n this.subscribe(TOPIC_DATAVIEW_ADDED, async () => {\n await this.refreshPersistedList(true);\n if (this.autoActivateTab) this.activateContainingTab();\n });\n await this.refreshPersistedList(false);\n }\n\n private async refreshPersistedList(selectLatest: boolean): Promise<void> {\n this.loadingList = true;\n this.requestUpdate();\n try {\n this.persistedList = await dataviewerService.listViews();\n if (selectLatest && this.persistedList.length > 0) {\n const latest = this.persistedList[this.persistedList.length - 1];\n this.selectedStorageKey = latest.storageKey;\n this.selectedView = await dataviewerService.getView(latest.storageKey);\n } else if (this.selectedStorageKey) {\n this.selectedView = await dataviewerService.getView(this.selectedStorageKey);\n } else {\n this.selectedView = null;\n }\n } catch (e) {\n toastError(e instanceof Error ? e.message : String(e));\n this.persistedList = [];\n this.selectedView = null;\n } finally {\n this.loadingList = false;\n this.requestUpdate();\n }\n }\n\n private async selectStorageKey(key: string): Promise<void> {\n this.selectedStorageKey = key;\n if (!key) {\n this.selectedView = null;\n this.requestUpdate();\n return;\n }\n try {\n this.selectedView = await dataviewerService.getView(key);\n } catch (err) {\n toastError(err instanceof Error ? err.message : String(err));\n this.selectedView = null;\n }\n this.requestUpdate();\n }\n\n private async onAutoActivateChange(e: Event): Promise<void> {\n const checked = (e.target as HTMLInputElement).checked;\n this.autoActivateTab = checked;\n const current = (await this.getDialogSetting()) ?? {};\n await this.setDialogSetting({ ...current, autoActivateTab: checked });\n }\n\n private async onHistorySelect(e: CustomEvent<{ item?: { value?: string } }>): Promise<void> {\n const value = e.detail?.item?.value ?? '';\n if (!value || value === '__stats__') {\n return;\n }\n await this.selectStorageKey(value);\n }\n\n private async onDeleteView(e: Event, storageKey: string): Promise<void> {\n e.stopPropagation();\n e.preventDefault();\n try {\n await dataviewerService.deleteView(storageKey);\n const deletedWasSelected = this.selectedStorageKey === storageKey;\n if (deletedWasSelected) {\n this.selectedStorageKey = '';\n this.selectedView = null;\n }\n await this.refreshPersistedList(true);\n } catch (err) {\n toastError(err instanceof Error ? err.message : String(err));\n }\n }\n\n private async onClearHistory(): Promise<void> {\n try {\n await dataviewerService.clearAllViews();\n this.selectedStorageKey = '';\n this.selectedView = null;\n await this.refreshPersistedList(false);\n } catch (err) {\n toastError(err instanceof Error ? err.message : String(err));\n }\n }\n\n protected renderToolbar() {\n const current = this.selectedView ?? this.dataview;\n const selectedMeta = this.persistedList.find((e) => e.storageKey === this.selectedStorageKey);\n const baseTitle = selectedMeta?.title ?? current?.title ?? (this.persistedList.length > 0 ? 'Latest data view' : 'No data');\n const formattedCreatedAt =\n (selectedMeta?.createdAt ?? (current as any)?.createdAt)\n ? new Date(selectedMeta?.createdAt ?? (current as any)?.createdAt).toLocaleString()\n : null;\n const engineLabel = current?.source ?? null;\n const titleWithEngine = engineLabel ? `${baseTitle} · ${engineLabel}` : baseTitle;\n const currentLabel = formattedCreatedAt ? `${titleWithEngine} (${formattedCreatedAt})` : titleWithEngine;\n return html`\n <wa-dropdown\n placement=\"bottom-start\"\n distance=\"4\"\n size=\"s\"\n hoist\n @wa-select=${(e: CustomEvent) => this.onHistorySelect(e as any)}\n >\n <wa-button\n slot=\"trigger\"\n appearance=\"plain\"\n size=\"s\"\n with-caret\n title=\"Data view history\"\n >\n <wa-icon name=\"clock-rotate-left\" label=\"History\"></wa-icon>\n </wa-button>\n\n <wa-dropdown-item value=\"__stats__\">\n ${this.persistedList.length} data view${this.persistedList.length === 1 ? '' : 's'}\n ${this.persistedList.length > 0\n ? html`\n <wa-button\n slot=\"details\"\n appearance=\"plain\"\n size=\"s\"\n title=\"Clear history\"\n @click=${() => this.onClearHistory()}\n >\n <wa-icon name=\"trash\" label=\"Clear history\"></wa-icon>\n </wa-button>\n `\n : null}\n </wa-dropdown-item>\n\n ${this.persistedList.map(\n (entry) => html`\n <wa-dropdown-item value=${entry.storageKey}>\n ${entry.source ? `${entry.title} · ${entry.source}` : entry.title}\n ${entry.createdAt\n ? html`<span style=\"opacity: 0.7; margin-left: 0.5rem; font-size: 0.75em;\">\n (${new Date(entry.createdAt).toLocaleString()})\n </span>`\n : null}\n <wa-button\n slot=\"details\"\n appearance=\"plain\"\n size=\"s\"\n title=\"Delete data view\"\n @click=${(e: Event) => this.onDeleteView(e, entry.storageKey)}\n >\n <wa-icon name=\"trash\" label=\"Delete\"></wa-icon>\n </wa-button>\n </wa-dropdown-item>\n `\n )}\n\n </wa-dropdown>\n\n <wa-divider orientation=\"vertical\"></wa-divider>\n\n <wa-button\n size=\"s\"\n appearance=\"plain\"\n title=\"Export current data view to CSV\"\n ?disabled=${!this.hasData}\n @click=${() => this.onExportCsv()}\n >\n <wa-icon name=\"file-csv\" label=\"Export CSV\"></wa-icon>\n </wa-button>\n\n <wa-switch\n ?checked=${this.autoActivateTab}\n size=\"s\"\n title=\"Switch to this tab when new results arrive\"\n @change=${(e: Event) => this.onAutoActivateChange(e)}\n style=\"margin-top: 0.5rem;\"\n >\n Auto-show\n </wa-switch>\n\n ${when(current, () => html`<wa-divider orientation=\"vertical\"></wa-divider><span>${currentLabel}</span>`)}\n `;\n }\n\n private renderTable(dv: DataView) {\n if (!this.hasData) {\n return html`<div class=\"result-empty\">No data.</div>`;\n }\n return html`<docks-data-table .data=${dv.data}></docks-data-table>`;\n }\n\n protected renderContent() {\n const dv = this.displayed;\n if (dv != null) return this.renderTable(dv);\n return html`<div class=\"result-empty\">No data.</div>`;\n }\n\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n .result-empty {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n }\n `;\n\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'docks-dataview': DataViewPart;\n }\n}\n","import Papa from 'papaparse';\n\n/** Parses CSV-like (comma-, tab-, or other delimited) text; delimiter is auto-detected. */\nexport function parseCsv(text: string): { columns: string[]; rows: unknown[][] } {\n const result = Papa.parse<Record<string, unknown>>(text, {\n header: true,\n skipEmptyLines: true,\n });\n const columns = result.meta.fields ?? [];\n const rows = result.data.map((row: Record<string, unknown>) =>\n columns.map((col: string) => row[col])\n );\n return { columns, rows };\n}\n","import { html } from 'lit';\nimport {\n rootContext,\n editorRegistry,\n contributionRegistry,\n File,\n type EditorInput,\n} from '@eclipse-docks/core';\nimport { PANEL_BOTTOM } from '@eclipse-docks/core';\nimport { dataviewerService } from './dataviewer-service';\nimport './dataview-part';\nimport type { DataView } from './api';\nimport { parseCsv } from './parse-csv';\n\ndataviewerService.init();\nrootContext.put('dataviewerService', dataviewerService);\n\ncontributionRegistry.registerContribution(PANEL_BOTTOM, {\n name: 'view.dataviewer',\n label: 'Data Views',\n icon: 'table',\n component: (id: string) => html`<docks-dataview id=\"${id}\"></docks-dataview>`,\n});\n\neditorRegistry.registerEditorInputHandler({\n editorId: 'system.dataviewer-table',\n label: 'Table',\n icon: 'table',\n ranking: 800,\n canHandle: (input: unknown) => {\n if (!(input instanceof File)) return false;\n const lower = input.getName().toLowerCase();\n return lower.endsWith('.csv') || lower.endsWith('.tsv');\n },\n handle: async (input: File) => {\n const name = input.getName();\n const text = (await input.getContents()) as string;\n const { columns, rows } = parseCsv(text ?? '');\n const data = { columns, rows };\n const editorInput: EditorInput = {\n title: input.getWorkspacePath(),\n data,\n key: input.getWorkspacePath(),\n icon: 'table',\n state: {},\n component: () => html`<docks-data-table .data=${data}></docks-data-table>`,\n };\n return editorInput;\n },\n});\n\nexport default function () {}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ extensionRegistry.registerExtension({
5
5
  id: pkg.name,
6
6
  name: "Data Viewer",
7
7
  description: "Tabular data views, persistence, and CSV/TSV/DuckDB integration",
8
- loader: () => import("./dataviewer-extension-BW9umf3B.js"),
8
+ loader: () => import("./dataviewer-extension-DhE3y4Rw.js"),
9
9
  icon: "table"
10
10
  });
11
11
  //#endregion
package/dist/table.js CHANGED
@@ -142,7 +142,7 @@ var DocksDataTable = class DocksDataTable extends LitElement {
142
142
  @input=${this.onFilterInput}
143
143
  @wa-clear=${this.clearFilter}
144
144
  with-clear
145
- size="small"
145
+ size="s"
146
146
  aria-label="Filter rows"
147
147
  >
148
148
  <wa-icon slot="start" name="magnifying-glass" label="Filter"></wa-icon>
@@ -150,7 +150,7 @@ var DocksDataTable = class DocksDataTable extends LitElement {
150
150
  <div class="paging-controls">
151
151
  <wa-select
152
152
  class="page-size-select"
153
- size="small"
153
+ size="s"
154
154
  .value=${String(this.pageSize)}
155
155
  title="Rows per page"
156
156
  @change=${this.onPageSizeChange}
@@ -161,7 +161,7 @@ var DocksDataTable = class DocksDataTable extends LitElement {
161
161
  ${total === 0 ? "0 rows" : `${start}–${end} of ${total}`}
162
162
  </span>
163
163
  <wa-button
164
- size="small"
164
+ size="s"
165
165
  appearance="plain"
166
166
  title="Previous page"
167
167
  ?disabled=${count <= 1 || page <= 0}
@@ -170,7 +170,7 @@ var DocksDataTable = class DocksDataTable extends LitElement {
170
170
  <wa-icon name="chevron-left" label="Previous"></wa-icon>
171
171
  </wa-button>
172
172
  <wa-button
173
- size="small"
173
+ size="s"
174
174
  appearance="plain"
175
175
  title="Next page"
176
176
  ?disabled=${count <= 1 || page >= count - 1}
package/dist/table.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"table.js","names":[],"sources":["../src/docks-data-table.ts"],"sourcesContent":["import { LitElement } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { css, html } from 'lit';\nimport type { TabularData } from './api';\n\ntype SortDirection = 'asc' | 'desc';\n\nfunction cellString(value: unknown): string {\n if (value === null || value === undefined) return '';\n return String(value);\n}\n\nfunction isNumericColumn(rows: unknown[][], colIndex: number): boolean {\n if (rows.length === 0) return false;\n return rows.every((row) => {\n const v = row[colIndex];\n if (v === null || v === undefined) return true;\n const n = Number(v);\n return Number.isFinite(n);\n });\n}\n\nfunction compareCells(a: unknown, b: unknown, numeric: boolean): number {\n if (numeric) {\n const na = Number(a);\n const nb = Number(b);\n if (!Number.isFinite(na)) return Number.isFinite(nb) ? 1 : 0;\n if (!Number.isFinite(nb)) return -1;\n return na - nb;\n }\n return cellString(a).localeCompare(cellString(b), undefined, { numeric: true });\n}\n\n@customElement('docks-data-table')\nexport class DocksDataTable extends LitElement {\n @property({ attribute: false })\n data: TabularData = { columns: [], rows: [] };\n\n @property({ type: String })\n emptyMessage = 'No data.';\n\n @state()\n private sortColumnIndex: number | null = null;\n\n @state()\n private sortDirection: SortDirection = 'asc';\n\n @state()\n private filterQuery = '';\n\n @state()\n private pageSize = 25;\n\n @state()\n private currentPage = 0;\n\n private static readonly PAGE_SIZE_OPTIONS = [10, 25, 50, 100];\n\n private get columns(): string[] {\n return Array.isArray(this.data?.columns) ? this.data.columns : [];\n }\n\n private get rows(): unknown[][] {\n return Array.isArray(this.data?.rows) ? this.data.rows : [];\n }\n\n private get filteredRows(): unknown[][] {\n const q = this.filterQuery.trim().toLowerCase();\n if (!q) return this.rows;\n return this.rows.filter((row) =>\n row.some((cell) => cellString(cell).toLowerCase().includes(q))\n );\n }\n\n private get sortedRows(): unknown[][] {\n const filtered = this.filteredRows;\n if (this.sortColumnIndex == null || this.sortColumnIndex < 0) return filtered;\n const col = this.sortColumnIndex;\n const numeric = isNumericColumn(filtered, col);\n const dir = this.sortDirection === 'asc' ? 1 : -1;\n return [...filtered].sort((rowA, rowB) => {\n const a = rowA[col];\n const b = rowB[col];\n return dir * compareCells(a, b, numeric);\n });\n }\n\n private get totalRows(): number {\n return this.sortedRows.length;\n }\n\n private get pageCount(): number {\n const total = this.totalRows;\n if (total === 0) return 1;\n return Math.ceil(total / this.pageSize);\n }\n\n private get pagedRows(): unknown[][] {\n const all = this.sortedRows;\n const page = this.clampedPage;\n const start = page * this.pageSize;\n return all.slice(start, start + this.pageSize);\n }\n\n private get clampedPage(): number {\n const count = this.pageCount;\n return count <= 0 ? 0 : Math.min(this.currentPage, count - 1);\n }\n\n private goToPage(page: number): void {\n const last = Math.max(0, this.pageCount - 1);\n this.currentPage = Math.max(0, Math.min(page, last));\n this.requestUpdate();\n }\n\n private onPageSizeChange(e: Event): void {\n const val = (e.target as HTMLSelectElement).value;\n const n = parseInt(val, 10);\n if (!Number.isFinite(n) || n < 1) return;\n this.pageSize = n;\n this.currentPage = 0;\n this.requestUpdate();\n }\n\n private onSort(colIndex: number): void {\n if (this.sortColumnIndex === colIndex) {\n this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';\n } else {\n this.sortColumnIndex = colIndex;\n this.sortDirection = 'asc';\n }\n this.requestUpdate();\n }\n\n private onFilterInput(e: Event): void {\n this.filterQuery = (e.target as HTMLInputElement).value;\n this.requestUpdate();\n }\n\n private clearFilter(): void {\n this.filterQuery = '';\n this.requestUpdate();\n }\n\n private getSortAria(colIndex: number): string {\n if (this.sortColumnIndex !== colIndex) return 'none';\n return this.sortDirection === 'asc' ? 'ascending' : 'descending';\n }\n\n render() {\n const { columns } = this;\n const total = this.totalRows;\n const displayRows = this.pagedRows;\n const page = this.clampedPage;\n const count = this.pageCount;\n const start = total === 0 ? 0 : page * this.pageSize + 1;\n const end = Math.min((page + 1) * this.pageSize, total);\n\n if (columns.length === 0 && total === 0 && this.rows.length === 0) {\n return html`<div class=\"table-empty\">${this.emptyMessage}</div>`;\n }\n\n return html`\n <div class=\"table-toolbar\">\n <wa-input\n class=\"filter-input\"\n placeholder=\"Filter…\"\n .value=${this.filterQuery}\n @input=${this.onFilterInput}\n @wa-clear=${this.clearFilter}\n with-clear\n size=\"small\"\n aria-label=\"Filter rows\"\n >\n <wa-icon slot=\"start\" name=\"magnifying-glass\" label=\"Filter\"></wa-icon>\n </wa-input>\n <div class=\"paging-controls\">\n <wa-select\n class=\"page-size-select\"\n size=\"small\"\n .value=${String(this.pageSize)}\n title=\"Rows per page\"\n @change=${this.onPageSizeChange}\n >\n ${DocksDataTable.PAGE_SIZE_OPTIONS.map(\n (n) => html`<wa-option value=${String(n)}>${n}</wa-option>`\n )}\n </wa-select>\n <span class=\"paging-summary\" aria-live=\"polite\">\n ${total === 0 ? '0 rows' : `${start}–${end} of ${total}`}\n </span>\n <wa-button\n size=\"small\"\n appearance=\"plain\"\n title=\"Previous page\"\n ?disabled=${count <= 1 || page <= 0}\n @click=${() => this.goToPage(page - 1)}\n >\n <wa-icon name=\"chevron-left\" label=\"Previous\"></wa-icon>\n </wa-button>\n <wa-button\n size=\"small\"\n appearance=\"plain\"\n title=\"Next page\"\n ?disabled=${count <= 1 || page >= count - 1}\n @click=${() => this.goToPage(page + 1)}\n >\n <wa-icon name=\"chevron-right\" label=\"Next\"></wa-icon>\n </wa-button>\n </div>\n </div>\n <div class=\"table-wrap\">\n <table class=\"result-table\">\n <thead>\n <tr>\n ${columns.map(\n (col, i) => html`\n <th scope=\"col\" role=\"columnheader\" aria-sort=${this.getSortAria(i)}>\n <button\n type=\"button\"\n class=\"th-sort\"\n @click=${() => this.onSort(i)}\n title=\"Sort by ${col}\"\n >\n <span class=\"th-label\">${col}</span>\n ${this.sortColumnIndex === i\n ? html`<wa-icon\n name=${this.sortDirection === 'asc' ? 'arrow-up' : 'arrow-down'}\n label=${this.sortDirection}\n ></wa-icon>`\n : html`<wa-icon name=\"arrows-up-down\" label=\"Sort\"></wa-icon>`}\n </button>\n </th>\n `\n )}\n </tr>\n </thead>\n <tbody>\n ${displayRows.length === 0\n ? html`<tr><td colspan=${columns.length} class=\"table-empty-cell\">No matching rows.</td></tr>`\n : displayRows.map(\n (row) => html`\n <tr>\n ${row.map((cell) => html`<td>${cellString(cell)}</td>`)}\n </tr>\n `\n )}\n </tbody>\n </table>\n </div>\n `;\n }\n\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n }\n .table-empty {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n }\n .table-toolbar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.25rem 0;\n flex-wrap: wrap;\n }\n .filter-input {\n max-width: 280px;\n }\n .paging-controls {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-left: auto;\n }\n .page-size-select {\n }\n .paging-summary {\n font-size: 0.8125rem;\n color: var(--wa-color-text-quiet);\n min-width: 5rem;\n }\n .table-wrap {\n flex: 1;\n min-height: 0;\n overflow: auto;\n border: 1px solid var(--wa-color-neutral-border-quiet);\n border-radius: var(--wa-border-radius-medium, 0.25rem);\n }\n .result-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.875rem;\n color: var(--wa-color-text-normal);\n }\n .result-table th,\n .result-table td {\n padding: 0.5rem 0.75rem;\n text-align: left;\n border-bottom: 1px solid var(--wa-color-neutral-border-quiet);\n }\n .result-table th {\n position: sticky;\n top: 0;\n z-index: 1;\n background: var(--wa-color-surface-lowered);\n font-weight: 600;\n white-space: nowrap;\n color: var(--wa-color-text-normal);\n box-shadow: 0 1px 0 0 var(--wa-color-neutral-border-quiet);\n }\n .result-table tbody tr:nth-child(even) td {\n background: var(--wa-color-surface-default);\n }\n .result-table tbody tr:nth-child(odd) td {\n background: var(--wa-color-surface-lowered);\n }\n .result-table tbody tr:hover td {\n background: var(--wa-color-neutral-fill-normal);\n }\n .th-sort {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n width: 100%;\n padding: 0;\n border: none;\n background: none;\n font: inherit;\n cursor: pointer;\n color: inherit;\n text-align: left;\n }\n .th-sort:hover {\n opacity: 0.85;\n }\n .th-label {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .th-sort wa-icon {\n flex-shrink: 0;\n opacity: 0.7;\n font-size: 0.75em;\n }\n .table-empty-cell {\n color: var(--wa-color-text-quiet);\n font-style: italic;\n text-align: center;\n }\n `;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'docks-data-table': DocksDataTable;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"table.js","names":[],"sources":["../src/docks-data-table.ts"],"sourcesContent":["import { LitElement } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { css, html } from 'lit';\nimport type { TabularData } from './api';\n\ntype SortDirection = 'asc' | 'desc';\n\nfunction cellString(value: unknown): string {\n if (value === null || value === undefined) return '';\n return String(value);\n}\n\nfunction isNumericColumn(rows: unknown[][], colIndex: number): boolean {\n if (rows.length === 0) return false;\n return rows.every((row) => {\n const v = row[colIndex];\n if (v === null || v === undefined) return true;\n const n = Number(v);\n return Number.isFinite(n);\n });\n}\n\nfunction compareCells(a: unknown, b: unknown, numeric: boolean): number {\n if (numeric) {\n const na = Number(a);\n const nb = Number(b);\n if (!Number.isFinite(na)) return Number.isFinite(nb) ? 1 : 0;\n if (!Number.isFinite(nb)) return -1;\n return na - nb;\n }\n return cellString(a).localeCompare(cellString(b), undefined, { numeric: true });\n}\n\n@customElement('docks-data-table')\nexport class DocksDataTable extends LitElement {\n @property({ attribute: false })\n data: TabularData = { columns: [], rows: [] };\n\n @property({ type: String })\n emptyMessage = 'No data.';\n\n @state()\n private sortColumnIndex: number | null = null;\n\n @state()\n private sortDirection: SortDirection = 'asc';\n\n @state()\n private filterQuery = '';\n\n @state()\n private pageSize = 25;\n\n @state()\n private currentPage = 0;\n\n private static readonly PAGE_SIZE_OPTIONS = [10, 25, 50, 100];\n\n private get columns(): string[] {\n return Array.isArray(this.data?.columns) ? this.data.columns : [];\n }\n\n private get rows(): unknown[][] {\n return Array.isArray(this.data?.rows) ? this.data.rows : [];\n }\n\n private get filteredRows(): unknown[][] {\n const q = this.filterQuery.trim().toLowerCase();\n if (!q) return this.rows;\n return this.rows.filter((row) =>\n row.some((cell) => cellString(cell).toLowerCase().includes(q))\n );\n }\n\n private get sortedRows(): unknown[][] {\n const filtered = this.filteredRows;\n if (this.sortColumnIndex == null || this.sortColumnIndex < 0) return filtered;\n const col = this.sortColumnIndex;\n const numeric = isNumericColumn(filtered, col);\n const dir = this.sortDirection === 'asc' ? 1 : -1;\n return [...filtered].sort((rowA, rowB) => {\n const a = rowA[col];\n const b = rowB[col];\n return dir * compareCells(a, b, numeric);\n });\n }\n\n private get totalRows(): number {\n return this.sortedRows.length;\n }\n\n private get pageCount(): number {\n const total = this.totalRows;\n if (total === 0) return 1;\n return Math.ceil(total / this.pageSize);\n }\n\n private get pagedRows(): unknown[][] {\n const all = this.sortedRows;\n const page = this.clampedPage;\n const start = page * this.pageSize;\n return all.slice(start, start + this.pageSize);\n }\n\n private get clampedPage(): number {\n const count = this.pageCount;\n return count <= 0 ? 0 : Math.min(this.currentPage, count - 1);\n }\n\n private goToPage(page: number): void {\n const last = Math.max(0, this.pageCount - 1);\n this.currentPage = Math.max(0, Math.min(page, last));\n this.requestUpdate();\n }\n\n private onPageSizeChange(e: Event): void {\n const val = (e.target as HTMLSelectElement).value;\n const n = parseInt(val, 10);\n if (!Number.isFinite(n) || n < 1) return;\n this.pageSize = n;\n this.currentPage = 0;\n this.requestUpdate();\n }\n\n private onSort(colIndex: number): void {\n if (this.sortColumnIndex === colIndex) {\n this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';\n } else {\n this.sortColumnIndex = colIndex;\n this.sortDirection = 'asc';\n }\n this.requestUpdate();\n }\n\n private onFilterInput(e: Event): void {\n this.filterQuery = (e.target as HTMLInputElement).value;\n this.requestUpdate();\n }\n\n private clearFilter(): void {\n this.filterQuery = '';\n this.requestUpdate();\n }\n\n private getSortAria(colIndex: number): string {\n if (this.sortColumnIndex !== colIndex) return 'none';\n return this.sortDirection === 'asc' ? 'ascending' : 'descending';\n }\n\n render() {\n const { columns } = this;\n const total = this.totalRows;\n const displayRows = this.pagedRows;\n const page = this.clampedPage;\n const count = this.pageCount;\n const start = total === 0 ? 0 : page * this.pageSize + 1;\n const end = Math.min((page + 1) * this.pageSize, total);\n\n if (columns.length === 0 && total === 0 && this.rows.length === 0) {\n return html`<div class=\"table-empty\">${this.emptyMessage}</div>`;\n }\n\n return html`\n <div class=\"table-toolbar\">\n <wa-input\n class=\"filter-input\"\n placeholder=\"Filter…\"\n .value=${this.filterQuery}\n @input=${this.onFilterInput}\n @wa-clear=${this.clearFilter}\n with-clear\n size=\"s\"\n aria-label=\"Filter rows\"\n >\n <wa-icon slot=\"start\" name=\"magnifying-glass\" label=\"Filter\"></wa-icon>\n </wa-input>\n <div class=\"paging-controls\">\n <wa-select\n class=\"page-size-select\"\n size=\"s\"\n .value=${String(this.pageSize)}\n title=\"Rows per page\"\n @change=${this.onPageSizeChange}\n >\n ${DocksDataTable.PAGE_SIZE_OPTIONS.map(\n (n) => html`<wa-option value=${String(n)}>${n}</wa-option>`\n )}\n </wa-select>\n <span class=\"paging-summary\" aria-live=\"polite\">\n ${total === 0 ? '0 rows' : `${start}–${end} of ${total}`}\n </span>\n <wa-button\n size=\"s\"\n appearance=\"plain\"\n title=\"Previous page\"\n ?disabled=${count <= 1 || page <= 0}\n @click=${() => this.goToPage(page - 1)}\n >\n <wa-icon name=\"chevron-left\" label=\"Previous\"></wa-icon>\n </wa-button>\n <wa-button\n size=\"s\"\n appearance=\"plain\"\n title=\"Next page\"\n ?disabled=${count <= 1 || page >= count - 1}\n @click=${() => this.goToPage(page + 1)}\n >\n <wa-icon name=\"chevron-right\" label=\"Next\"></wa-icon>\n </wa-button>\n </div>\n </div>\n <div class=\"table-wrap\">\n <table class=\"result-table\">\n <thead>\n <tr>\n ${columns.map(\n (col, i) => html`\n <th scope=\"col\" role=\"columnheader\" aria-sort=${this.getSortAria(i)}>\n <button\n type=\"button\"\n class=\"th-sort\"\n @click=${() => this.onSort(i)}\n title=\"Sort by ${col}\"\n >\n <span class=\"th-label\">${col}</span>\n ${this.sortColumnIndex === i\n ? html`<wa-icon\n name=${this.sortDirection === 'asc' ? 'arrow-up' : 'arrow-down'}\n label=${this.sortDirection}\n ></wa-icon>`\n : html`<wa-icon name=\"arrows-up-down\" label=\"Sort\"></wa-icon>`}\n </button>\n </th>\n `\n )}\n </tr>\n </thead>\n <tbody>\n ${displayRows.length === 0\n ? html`<tr><td colspan=${columns.length} class=\"table-empty-cell\">No matching rows.</td></tr>`\n : displayRows.map(\n (row) => html`\n <tr>\n ${row.map((cell) => html`<td>${cellString(cell)}</td>`)}\n </tr>\n `\n )}\n </tbody>\n </table>\n </div>\n `;\n }\n\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n }\n .table-empty {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n }\n .table-toolbar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.25rem 0;\n flex-wrap: wrap;\n }\n .filter-input {\n max-width: 280px;\n }\n .paging-controls {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-left: auto;\n }\n .page-size-select {\n }\n .paging-summary {\n font-size: 0.8125rem;\n color: var(--wa-color-text-quiet);\n min-width: 5rem;\n }\n .table-wrap {\n flex: 1;\n min-height: 0;\n overflow: auto;\n border: 1px solid var(--wa-color-neutral-border-quiet);\n border-radius: var(--wa-border-radius-medium, 0.25rem);\n }\n .result-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.875rem;\n color: var(--wa-color-text-normal);\n }\n .result-table th,\n .result-table td {\n padding: 0.5rem 0.75rem;\n text-align: left;\n border-bottom: 1px solid var(--wa-color-neutral-border-quiet);\n }\n .result-table th {\n position: sticky;\n top: 0;\n z-index: 1;\n background: var(--wa-color-surface-lowered);\n font-weight: 600;\n white-space: nowrap;\n color: var(--wa-color-text-normal);\n box-shadow: 0 1px 0 0 var(--wa-color-neutral-border-quiet);\n }\n .result-table tbody tr:nth-child(even) td {\n background: var(--wa-color-surface-default);\n }\n .result-table tbody tr:nth-child(odd) td {\n background: var(--wa-color-surface-lowered);\n }\n .result-table tbody tr:hover td {\n background: var(--wa-color-neutral-fill-normal);\n }\n .th-sort {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n width: 100%;\n padding: 0;\n border: none;\n background: none;\n font: inherit;\n cursor: pointer;\n color: inherit;\n text-align: left;\n }\n .th-sort:hover {\n opacity: 0.85;\n }\n .th-label {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .th-sort wa-icon {\n flex-shrink: 0;\n opacity: 0.7;\n font-size: 0.75em;\n }\n .table-empty-cell {\n color: var(--wa-color-text-quiet);\n font-style: italic;\n text-align: center;\n }\n `;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'docks-data-table': DocksDataTable;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclipse-docks/extension-dataviewer",
3
- "version": "0.7.122",
3
+ "version": "0.7.124",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/eclipse-docks/core"
@@ -1 +0,0 @@
1
- {"version":3,"file":"dataviewer-extension-BW9umf3B.js","names":[],"sources":["../src/dataviewer-service.ts","../src/dataview-part.ts","../src/parse-csv.ts","../src/dataviewer-extension.ts"],"sourcesContent":["import { persistenceService, subscribe, publish } from '@eclipse-docks/core';\nimport { v4 } from '@eclipse-docks/core/externals/third-party';\nimport type { DataView } from './api';\nimport { TOPIC_DATAVIEW_PUBLISH, TOPIC_DATAVIEW_ADDED } from './api';\n\nconst KEY_PREFIX = 'dataview/';\nconst KEY_INDEX = KEY_PREFIX + 'index';\n\nexport interface DataviewListEntry {\n storageKey: string;\n title: string;\n source?: string;\n createdAt: number;\n}\n\ntype IndexEntry = DataviewListEntry;\n\nexport class DataviewerService {\n private subscriptionToken?: string;\n\n init(): void {\n if (this.subscriptionToken !== undefined) return;\n this.subscriptionToken = subscribe(TOPIC_DATAVIEW_PUBLISH, (payload: DataView) => {\n void this.handlePublish(payload);\n });\n }\n\n private async handlePublish(payload: DataView): Promise<void> {\n const storageKey = v4();\n const createdAt = Date.now();\n const entry: DataView = {\n id: payload.id ?? storageKey,\n title: payload.title,\n data: payload.data,\n source: payload.source,\n createdAt,\n };\n await persistenceService.persistObject(KEY_PREFIX + storageKey, entry);\n const index = (await persistenceService.getObject(KEY_INDEX)) as IndexEntry[] | undefined;\n const list = Array.isArray(index) ? index : [];\n list.push({ storageKey, title: payload.title, source: payload.source, createdAt });\n await persistenceService.persistObject(KEY_INDEX, list);\n publish(TOPIC_DATAVIEW_ADDED, { storageKey, title: payload.title, createdAt });\n }\n\n async listViews(): Promise<DataviewListEntry[]> {\n const raw = await persistenceService.getObject(KEY_INDEX);\n if (!Array.isArray(raw) || raw.length === 0) return [];\n if (typeof raw[0] === 'string') {\n return (raw as string[]).map((storageKey) => ({\n storageKey,\n title: storageKey,\n createdAt: 0,\n }));\n }\n const list = raw as IndexEntry[];\n return [...list].sort((a, b) => a.createdAt - b.createdAt);\n }\n\n async getView(storageKey: string): Promise<DataView | null> {\n const entry = await persistenceService.getObject(KEY_PREFIX + storageKey);\n return entry ?? null;\n }\n\n async deleteView(storageKey: string): Promise<void> {\n const index = (await persistenceService.getObject(KEY_INDEX)) as IndexEntry[] | undefined;\n const list = Array.isArray(index) ? index.filter((e) => e.storageKey !== storageKey) : [];\n await persistenceService.persistObject(KEY_INDEX, list);\n await persistenceService.persistObject(KEY_PREFIX + storageKey, null);\n }\n\n async clearAllViews(): Promise<void> {\n const index = (await persistenceService.getObject(KEY_INDEX)) as IndexEntry[] | undefined;\n const list = Array.isArray(index) ? index : [];\n await Promise.all(list.map((entry) => persistenceService.persistObject(KEY_PREFIX + entry.storageKey, null)));\n await persistenceService.persistObject(KEY_INDEX, []);\n }\n}\n\nexport const dataviewerService = new DataviewerService();\n","import { customElement, property, state } from 'lit/decorators.js';\nimport { css, html } from 'lit';\nimport { DocksPart, toastError, filebrowserDialog } from '@eclipse-docks/core';\nimport { dataviewerService, type DataviewListEntry } from './dataviewer-service';\nimport type { DataView } from './api';\nimport { TOPIC_DATAVIEW_ADDED } from './api';\nimport { when } from '@eclipse-docks/core/externals/lit';\nimport './docks-data-table';\n\n@customElement('docks-dataview')\nexport class DataViewPart extends DocksPart {\n @property({ attribute: false })\n dataview: DataView | null = null;\n\n @state()\n private persistedList: DataviewListEntry[] = [];\n\n @state()\n private selectedStorageKey = '';\n\n @state()\n private selectedView: DataView | null = null;\n\n @state()\n private loadingList = true;\n\n @state()\n private autoActivateTab = true;\n\n private get displayed(): DataView | null {\n return this.selectedView ?? this.dataview;\n }\n\n private get hasData(): boolean {\n const dv = this.displayed;\n if (!dv) return false;\n const { columns, rows } = dv.data;\n return Array.isArray(columns) && Array.isArray(rows) && (columns.length > 0 || rows.length > 0);\n }\n\n private toCsv(dv: DataView): string {\n const { columns, rows } = dv.data;\n const escapeCell = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n const str = String(value);\n if (!/[\",\\n]/.test(str)) return str;\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n };\n const header = columns.map(escapeCell).join(',');\n const body = rows.map((row) => row.map(escapeCell).join(',')).join('\\n');\n return body ? `${header}\\n${body}` : header;\n }\n\n private async onExportCsv(): Promise<void> {\n const dv = this.displayed;\n if (!dv || !this.hasData) return;\n try {\n const csv = this.toCsv(dv);\n const safeTitle = dv.title?.trim() || 'dataview';\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n const fileName = `${safeTitle.replace(/[^a-zA-Z0-9-_]+/g, '_')}-${timestamp}.csv`;\n const targetDir = await filebrowserDialog('directory');\n if (!targetDir) return;\n await this.executeCommand('touch', {\n path: `${targetDir}/${fileName}`,\n contents: csv,\n });\n } catch (err) {\n toastError(err instanceof Error ? err.message : String(err));\n }\n }\n\n protected async doInitUI() {\n const persisted = await this.getDialogSetting();\n if (persisted && typeof persisted.autoActivateTab === 'boolean') this.autoActivateTab = persisted.autoActivateTab;\n this.subscribe(TOPIC_DATAVIEW_ADDED, async () => {\n await this.refreshPersistedList(true);\n if (this.autoActivateTab) this.activateContainingTab();\n });\n await this.refreshPersistedList(false);\n }\n\n private async refreshPersistedList(selectLatest: boolean): Promise<void> {\n this.loadingList = true;\n this.requestUpdate();\n try {\n this.persistedList = await dataviewerService.listViews();\n if (selectLatest && this.persistedList.length > 0) {\n const latest = this.persistedList[this.persistedList.length - 1];\n this.selectedStorageKey = latest.storageKey;\n this.selectedView = await dataviewerService.getView(latest.storageKey);\n } else if (this.selectedStorageKey) {\n this.selectedView = await dataviewerService.getView(this.selectedStorageKey);\n } else {\n this.selectedView = null;\n }\n } catch (e) {\n toastError(e instanceof Error ? e.message : String(e));\n this.persistedList = [];\n this.selectedView = null;\n } finally {\n this.loadingList = false;\n this.requestUpdate();\n }\n }\n\n private async selectStorageKey(key: string): Promise<void> {\n this.selectedStorageKey = key;\n if (!key) {\n this.selectedView = null;\n this.requestUpdate();\n return;\n }\n try {\n this.selectedView = await dataviewerService.getView(key);\n } catch (err) {\n toastError(err instanceof Error ? err.message : String(err));\n this.selectedView = null;\n }\n this.requestUpdate();\n }\n\n private async onAutoActivateChange(e: Event): Promise<void> {\n const checked = (e.target as HTMLInputElement).checked;\n this.autoActivateTab = checked;\n const current = (await this.getDialogSetting()) ?? {};\n await this.setDialogSetting({ ...current, autoActivateTab: checked });\n }\n\n private async onHistorySelect(e: CustomEvent<{ item?: { value?: string } }>): Promise<void> {\n const value = e.detail?.item?.value ?? '';\n if (!value || value === '__stats__') {\n return;\n }\n await this.selectStorageKey(value);\n }\n\n private async onDeleteView(e: Event, storageKey: string): Promise<void> {\n e.stopPropagation();\n e.preventDefault();\n try {\n await dataviewerService.deleteView(storageKey);\n const deletedWasSelected = this.selectedStorageKey === storageKey;\n if (deletedWasSelected) {\n this.selectedStorageKey = '';\n this.selectedView = null;\n }\n await this.refreshPersistedList(true);\n } catch (err) {\n toastError(err instanceof Error ? err.message : String(err));\n }\n }\n\n private async onClearHistory(): Promise<void> {\n try {\n await dataviewerService.clearAllViews();\n this.selectedStorageKey = '';\n this.selectedView = null;\n await this.refreshPersistedList(false);\n } catch (err) {\n toastError(err instanceof Error ? err.message : String(err));\n }\n }\n\n protected renderToolbar() {\n const current = this.selectedView ?? this.dataview;\n const selectedMeta = this.persistedList.find((e) => e.storageKey === this.selectedStorageKey);\n const baseTitle = selectedMeta?.title ?? current?.title ?? (this.persistedList.length > 0 ? 'Latest data view' : 'No data');\n const formattedCreatedAt =\n (selectedMeta?.createdAt ?? (current as any)?.createdAt)\n ? new Date(selectedMeta?.createdAt ?? (current as any)?.createdAt).toLocaleString()\n : null;\n const engineLabel = current?.source ?? null;\n const titleWithEngine = engineLabel ? `${baseTitle} · ${engineLabel}` : baseTitle;\n const currentLabel = formattedCreatedAt ? `${titleWithEngine} (${formattedCreatedAt})` : titleWithEngine;\n return html`\n <wa-dropdown\n placement=\"bottom-start\"\n distance=\"4\"\n size=\"small\"\n hoist\n @wa-select=${(e: CustomEvent) => this.onHistorySelect(e as any)}\n >\n <wa-button\n slot=\"trigger\"\n appearance=\"plain\"\n size=\"small\"\n with-caret\n title=\"Data view history\"\n >\n <wa-icon name=\"clock-rotate-left\" label=\"History\"></wa-icon>\n </wa-button>\n\n <wa-dropdown-item value=\"__stats__\">\n ${this.persistedList.length} data view${this.persistedList.length === 1 ? '' : 's'}\n ${this.persistedList.length > 0\n ? html`\n <wa-button\n slot=\"details\"\n appearance=\"plain\"\n size=\"small\"\n title=\"Clear history\"\n @click=${() => this.onClearHistory()}\n >\n <wa-icon name=\"trash\" label=\"Clear history\"></wa-icon>\n </wa-button>\n `\n : null}\n </wa-dropdown-item>\n\n ${this.persistedList.map(\n (entry) => html`\n <wa-dropdown-item value=${entry.storageKey}>\n ${entry.source ? `${entry.title} · ${entry.source}` : entry.title}\n ${entry.createdAt\n ? html`<span style=\"opacity: 0.7; margin-left: 0.5rem; font-size: 0.75em;\">\n (${new Date(entry.createdAt).toLocaleString()})\n </span>`\n : null}\n <wa-button\n slot=\"details\"\n appearance=\"plain\"\n size=\"small\"\n title=\"Delete data view\"\n @click=${(e: Event) => this.onDeleteView(e, entry.storageKey)}\n >\n <wa-icon name=\"trash\" label=\"Delete\"></wa-icon>\n </wa-button>\n </wa-dropdown-item>\n `\n )}\n\n </wa-dropdown>\n\n <wa-divider orientation=\"vertical\"></wa-divider>\n\n <wa-button\n size=\"small\"\n appearance=\"plain\"\n title=\"Export current data view to CSV\"\n ?disabled=${!this.hasData}\n @click=${() => this.onExportCsv()}\n >\n <wa-icon name=\"file-csv\" label=\"Export CSV\"></wa-icon>\n </wa-button>\n\n <wa-switch\n ?checked=${this.autoActivateTab}\n size=\"small\"\n title=\"Switch to this tab when new results arrive\"\n @change=${(e: Event) => this.onAutoActivateChange(e)}\n style=\"margin-top: 0.5rem;\"\n >\n Auto-show\n </wa-switch>\n\n ${when(current, () => html`<wa-divider orientation=\"vertical\"></wa-divider><span>${currentLabel}</span>`)}\n `;\n }\n\n private renderTable(dv: DataView) {\n if (!this.hasData) {\n return html`<div class=\"result-empty\">No data.</div>`;\n }\n return html`<docks-data-table .data=${dv.data}></docks-data-table>`;\n }\n\n protected renderContent() {\n const dv = this.displayed;\n if (dv != null) return this.renderTable(dv);\n return html`<div class=\"result-empty\">No data.</div>`;\n }\n\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n .result-empty {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n }\n `;\n\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'docks-dataview': DataViewPart;\n }\n}\n","import Papa from 'papaparse';\n\n/** Parses CSV-like (comma-, tab-, or other delimited) text; delimiter is auto-detected. */\nexport function parseCsv(text: string): { columns: string[]; rows: unknown[][] } {\n const result = Papa.parse<Record<string, unknown>>(text, {\n header: true,\n skipEmptyLines: true,\n });\n const columns = result.meta.fields ?? [];\n const rows = result.data.map((row: Record<string, unknown>) =>\n columns.map((col: string) => row[col])\n );\n return { columns, rows };\n}\n","import { html } from 'lit';\nimport {\n rootContext,\n editorRegistry,\n contributionRegistry,\n File,\n type EditorInput,\n} from '@eclipse-docks/core';\nimport { PANEL_BOTTOM } from '@eclipse-docks/core';\nimport { dataviewerService } from './dataviewer-service';\nimport './dataview-part';\nimport type { DataView } from './api';\nimport { parseCsv } from './parse-csv';\n\ndataviewerService.init();\nrootContext.put('dataviewerService', dataviewerService);\n\ncontributionRegistry.registerContribution(PANEL_BOTTOM, {\n name: 'view.dataviewer',\n label: 'Data Views',\n icon: 'table',\n component: (id: string) => html`<docks-dataview id=\"${id}\"></docks-dataview>`,\n});\n\neditorRegistry.registerEditorInputHandler({\n editorId: 'system.dataviewer-table',\n label: 'Table',\n icon: 'table',\n ranking: 800,\n canHandle: (input: unknown) => {\n if (!(input instanceof File)) return false;\n const lower = input.getName().toLowerCase();\n return lower.endsWith('.csv') || lower.endsWith('.tsv');\n },\n handle: async (input: File) => {\n const name = input.getName();\n const text = (await input.getContents()) as string;\n const { columns, rows } = parseCsv(text ?? '');\n const data = { columns, rows };\n const editorInput: EditorInput = {\n title: input.getWorkspacePath(),\n data,\n key: input.getWorkspacePath(),\n icon: 'table',\n state: {},\n component: () => html`<docks-data-table .data=${data}></docks-data-table>`,\n };\n return editorInput;\n },\n});\n\nexport default function () {}\n"],"mappings":";;;;;;;;;;AAKA,IAAM,aAAa;AACnB,IAAM,YAAY,aAAa;AAW/B,IAAa,oBAAb,MAA+B;CAG7B,OAAa;AACX,MAAI,KAAK,sBAAsB,KAAA,EAAW;AAC1C,OAAK,oBAAoB,UAAU,yBAAyB,YAAsB;AAC3E,QAAK,cAAc,QAAQ;IAChC;;CAGJ,MAAc,cAAc,SAAkC;EAC5D,MAAM,aAAa,IAAI;EACvB,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,QAAkB;GACtB,IAAI,QAAQ,MAAM;GAClB,OAAO,QAAQ;GACf,MAAM,QAAQ;GACd,QAAQ,QAAQ;GAChB;GACD;AACD,QAAM,mBAAmB,cAAc,aAAa,YAAY,MAAM;EACtE,MAAM,QAAS,MAAM,mBAAmB,UAAU,UAAU;EAC5D,MAAM,OAAO,MAAM,QAAQ,MAAM,GAAG,QAAQ,EAAE;AAC9C,OAAK,KAAK;GAAE;GAAY,OAAO,QAAQ;GAAO,QAAQ,QAAQ;GAAQ;GAAW,CAAC;AAClF,QAAM,mBAAmB,cAAc,WAAW,KAAK;AACvD,UAAQ,sBAAsB;GAAE;GAAY,OAAO,QAAQ;GAAO;GAAW,CAAC;;CAGhF,MAAM,YAA0C;EAC9C,MAAM,MAAM,MAAM,mBAAmB,UAAU,UAAU;AACzD,MAAI,CAAC,MAAM,QAAQ,IAAI,IAAI,IAAI,WAAW,EAAG,QAAO,EAAE;AACtD,MAAI,OAAO,IAAI,OAAO,SACpB,QAAQ,IAAiB,KAAK,gBAAgB;GAC5C;GACA,OAAO;GACP,WAAW;GACZ,EAAE;AAGL,SAAO,CAAC,GADK,IACG,CAAC,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,UAAU;;CAG5D,MAAM,QAAQ,YAA8C;AAE1D,SADc,MAAM,mBAAmB,UAAU,aAAa,WAAW,IACzD;;CAGlB,MAAM,WAAW,YAAmC;EAClD,MAAM,QAAS,MAAM,mBAAmB,UAAU,UAAU;EAC5D,MAAM,OAAO,MAAM,QAAQ,MAAM,GAAG,MAAM,QAAQ,MAAM,EAAE,eAAe,WAAW,GAAG,EAAE;AACzF,QAAM,mBAAmB,cAAc,WAAW,KAAK;AACvD,QAAM,mBAAmB,cAAc,aAAa,YAAY,KAAK;;CAGvE,MAAM,gBAA+B;EACnC,MAAM,QAAS,MAAM,mBAAmB,UAAU,UAAU;EAC5D,MAAM,OAAO,MAAM,QAAQ,MAAM,GAAG,QAAQ,EAAE;AAC9C,QAAM,QAAQ,IAAI,KAAK,KAAK,UAAU,mBAAmB,cAAc,aAAa,MAAM,YAAY,KAAK,CAAC,CAAC;AAC7G,QAAM,mBAAmB,cAAc,WAAW,EAAE,CAAC;;;AAIzD,IAAa,oBAAoB,IAAI,mBAAmB;;;ACrEjD,IAAA,eAAA,MAAM,qBAAqB,UAAU;;;kBAEd;uBAGiB,EAAE;4BAGlB;sBAGW;qBAGlB;yBAGI;;CAE1B,IAAY,YAA6B;AACvC,SAAO,KAAK,gBAAgB,KAAK;;CAGnC,IAAY,UAAmB;EAC7B,MAAM,KAAK,KAAK;AAChB,MAAI,CAAC,GAAI,QAAO;EAChB,MAAM,EAAE,SAAS,SAAS,GAAG;AAC7B,SAAO,MAAM,QAAQ,QAAQ,IAAI,MAAM,QAAQ,KAAK,KAAK,QAAQ,SAAS,KAAK,KAAK,SAAS;;CAG/F,MAAc,IAAsB;EAClC,MAAM,EAAE,SAAS,SAAS,GAAG;EAC7B,MAAM,cAAc,UAA2B;AAC7C,OAAI,UAAU,QAAQ,UAAU,KAAA,EAAW,QAAO;GAClD,MAAM,MAAM,OAAO,MAAM;AACzB,OAAI,CAAC,SAAS,KAAK,IAAI,CAAE,QAAO;AAChC,UAAO,IAAI,IAAI,QAAQ,MAAM,OAAK,CAAC;;EAErC,MAAM,SAAS,QAAQ,IAAI,WAAW,CAAC,KAAK,IAAI;EAChD,MAAM,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAI,WAAW,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,KAAK;AACxE,SAAO,OAAO,GAAG,OAAO,IAAI,SAAS;;CAGvC,MAAc,cAA6B;EACzC,MAAM,KAAK,KAAK;AAChB,MAAI,CAAC,MAAM,CAAC,KAAK,QAAS;AAC1B,MAAI;GACF,MAAM,MAAM,KAAK,MAAM,GAAG;GAC1B,MAAM,YAAY,GAAG,OAAO,MAAM,IAAI;GACtC,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,SAAS,IAAI;GAChE,MAAM,WAAW,GAAG,UAAU,QAAQ,oBAAoB,IAAI,CAAC,GAAG,UAAU;GAC5E,MAAM,YAAY,MAAM,kBAAkB,YAAY;AACtD,OAAI,CAAC,UAAW;AAChB,SAAM,KAAK,eAAe,SAAS;IACjC,MAAM,GAAG,UAAU,GAAG;IACtB,UAAU;IACX,CAAC;WACK,KAAK;AACZ,cAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;;;CAIhE,MAAgB,WAAW;EACzB,MAAM,YAAY,MAAM,KAAK,kBAAkB;AAC/C,MAAI,aAAa,OAAO,UAAU,oBAAoB,UAAW,MAAK,kBAAkB,UAAU;AAClG,OAAK,UAAU,sBAAsB,YAAY;AAC/C,SAAM,KAAK,qBAAqB,KAAK;AACrC,OAAI,KAAK,gBAAiB,MAAK,uBAAuB;IACtD;AACF,QAAM,KAAK,qBAAqB,MAAM;;CAGxC,MAAc,qBAAqB,cAAsC;AACvE,OAAK,cAAc;AACnB,OAAK,eAAe;AACpB,MAAI;AACF,QAAK,gBAAgB,MAAM,kBAAkB,WAAW;AACxD,OAAI,gBAAgB,KAAK,cAAc,SAAS,GAAG;IACjD,MAAM,SAAS,KAAK,cAAc,KAAK,cAAc,SAAS;AAC9D,SAAK,qBAAqB,OAAO;AACjC,SAAK,eAAe,MAAM,kBAAkB,QAAQ,OAAO,WAAW;cAC7D,KAAK,mBACd,MAAK,eAAe,MAAM,kBAAkB,QAAQ,KAAK,mBAAmB;OAE5E,MAAK,eAAe;WAEf,GAAG;AACV,cAAW,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC;AACtD,QAAK,gBAAgB,EAAE;AACvB,QAAK,eAAe;YACZ;AACR,QAAK,cAAc;AACnB,QAAK,eAAe;;;CAIxB,MAAc,iBAAiB,KAA4B;AACzD,OAAK,qBAAqB;AAC1B,MAAI,CAAC,KAAK;AACR,QAAK,eAAe;AACpB,QAAK,eAAe;AACpB;;AAEF,MAAI;AACF,QAAK,eAAe,MAAM,kBAAkB,QAAQ,IAAI;WACjD,KAAK;AACZ,cAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;AAC5D,QAAK,eAAe;;AAEtB,OAAK,eAAe;;CAGtB,MAAc,qBAAqB,GAAyB;EAC1D,MAAM,UAAW,EAAE,OAA4B;AAC/C,OAAK,kBAAkB;EACvB,MAAM,UAAW,MAAM,KAAK,kBAAkB,IAAK,EAAE;AACrD,QAAM,KAAK,iBAAiB;GAAE,GAAG;GAAS,iBAAiB;GAAS,CAAC;;CAGvE,MAAc,gBAAgB,GAA8D;EAC1F,MAAM,QAAQ,EAAE,QAAQ,MAAM,SAAS;AACvC,MAAI,CAAC,SAAS,UAAU,YACtB;AAEF,QAAM,KAAK,iBAAiB,MAAM;;CAGpC,MAAc,aAAa,GAAU,YAAmC;AACtE,IAAE,iBAAiB;AACnB,IAAE,gBAAgB;AAClB,MAAI;AACF,SAAM,kBAAkB,WAAW,WAAW;AAE9C,OAD2B,KAAK,uBAAuB,YAC/B;AACtB,SAAK,qBAAqB;AAC1B,SAAK,eAAe;;AAEtB,SAAM,KAAK,qBAAqB,KAAK;WAC9B,KAAK;AACZ,cAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;;;CAIhE,MAAc,iBAAgC;AAC5C,MAAI;AACF,SAAM,kBAAkB,eAAe;AACvC,QAAK,qBAAqB;AAC1B,QAAK,eAAe;AACpB,SAAM,KAAK,qBAAqB,MAAM;WAC/B,KAAK;AACZ,cAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;;;CAIhE,gBAA0B;EACxB,MAAM,UAAU,KAAK,gBAAgB,KAAK;EAC1C,MAAM,eAAe,KAAK,cAAc,MAAM,MAAM,EAAE,eAAe,KAAK,mBAAmB;EAC7F,MAAM,YAAY,cAAc,SAAS,SAAS,UAAU,KAAK,cAAc,SAAS,IAAI,qBAAqB;EACjH,MAAM,qBACH,cAAc,aAAc,SAAiB,YAC1C,IAAI,KAAK,cAAc,aAAc,SAAiB,UAAU,CAAC,gBAAgB,GACjF;EACN,MAAM,cAAc,SAAS,UAAU;EACvC,MAAM,kBAAkB,cAAc,GAAG,UAAU,KAAK,gBAAgB;EACxE,MAAM,eAAe,qBAAqB,GAAG,gBAAgB,IAAI,mBAAmB,KAAK;AACzF,SAAO,IAAI;;;;;;wBAMS,MAAmB,KAAK,gBAAgB,EAAS,CAAC;;;;;;;;;;;;;cAa5D,KAAK,cAAc,OAAO,YAAY,KAAK,cAAc,WAAW,IAAI,KAAK,IAAI;cACjF,KAAK,cAAc,SAAS,IAC1B,IAAI;;;;;;mCAMe,KAAK,gBAAgB,CAAC;;;;oBAKzC,KAAK;;;YAGT,KAAK,cAAc,KAClB,UAAU,IAAI;wCACa,MAAM,WAAW;kBACvC,MAAM,SAAS,GAAG,MAAM,MAAM,KAAK,MAAM,WAAW,MAAM,MAAM;kBAChE,MAAM,YACJ,IAAI;yBACC,IAAI,KAAK,MAAM,UAAU,CAAC,gBAAgB,CAAC;+BAEhD,KAAK;;;;;;4BAMG,MAAa,KAAK,aAAa,GAAG,MAAM,WAAW,CAAC;;;;;cAMrE,CAAC;;;;;;;;;;sBAUU,CAAC,KAAK,QAAQ;yBACX,KAAK,aAAa,CAAC;;;;;;qBAMvB,KAAK,gBAAgB;;;qBAGrB,MAAa,KAAK,qBAAqB,EAAE,CAAC;;;;;;UAMrD,KAAK,eAAe,IAAI,yDAAyD,aAAa,SAAS,CAAC;;;CAIhH,YAAoB,IAAc;AAChC,MAAI,CAAC,KAAK,QACR,QAAO,IAAI;AAEb,SAAO,IAAI,2BAA2B,GAAG,KAAK;;CAGhD,gBAA0B;EACxB,MAAM,KAAK,KAAK;AAChB,MAAI,MAAM,KAAM,QAAO,KAAK,YAAY,GAAG;AAC3C,SAAO,IAAI;;;gBAGG,GAAG;;;;;;;;;;;;;;;;WAtQlB,SAAS,EAAE,WAAW,OAAO,CAAC,CAAA,EAAA,aAAA,WAAA,YAAA,KAAA,EAAA;WAG9B,OAAO,CAAA,EAAA,aAAA,WAAA,iBAAA,KAAA,EAAA;WAGP,OAAO,CAAA,EAAA,aAAA,WAAA,sBAAA,KAAA,EAAA;WAGP,OAAO,CAAA,EAAA,aAAA,WAAA,gBAAA,KAAA,EAAA;WAGP,OAAO,CAAA,EAAA,aAAA,WAAA,eAAA,KAAA,EAAA;WAGP,OAAO,CAAA,EAAA,aAAA,WAAA,mBAAA,KAAA,EAAA;0BAjBT,cAAc,iBAAiB,CAAA,EAAA,aAAA;;;;ACNhC,SAAgB,SAAS,MAAwD;CAC/E,MAAM,SAAS,KAAK,MAA+B,MAAM;EACvD,QAAQ;EACR,gBAAgB;EACjB,CAAC;CACF,MAAM,UAAU,OAAO,KAAK,UAAU,EAAE;AAIxC,QAAO;EAAE;EAAS,MAHL,OAAO,KAAK,KAAK,QAC5B,QAAQ,KAAK,QAAgB,IAAI,KAAK,CACvC;EACuB;;;;ACE1B,kBAAkB,MAAM;AACxB,YAAY,IAAI,qBAAqB,kBAAkB;AAEvD,qBAAqB,qBAAqB,cAAc;CACtD,MAAM;CACN,OAAO;CACP,MAAM;CACN,YAAY,OAAe,IAAI,uBAAuB,GAAG;CAC1D,CAAC;AAEF,eAAe,2BAA2B;CACxC,UAAU;CACV,OAAO;CACP,MAAM;CACN,SAAS;CACT,YAAY,UAAmB;AAC7B,MAAI,EAAE,iBAAiB,MAAO,QAAO;EACrC,MAAM,QAAQ,MAAM,SAAS,CAAC,aAAa;AAC3C,SAAO,MAAM,SAAS,OAAO,IAAI,MAAM,SAAS,OAAO;;CAEzD,QAAQ,OAAO,UAAgB;AAChB,QAAM,SAAS;EAE5B,MAAM,EAAE,SAAS,SAAS,SADZ,MAAM,MAAM,aAAa,IACI,GAAG;EAC9C,MAAM,OAAO;GAAE;GAAS;GAAM;AAS9B,SARiC;GAC/B,OAAO,MAAM,kBAAkB;GAC/B;GACA,KAAK,MAAM,kBAAkB;GAC7B,MAAM;GACN,OAAO,EAAE;GACT,iBAAiB,IAAI,2BAA2B,KAAK;GACtD;;CAGJ,CAAC;AAEF,SAAA,+BAA2B"}