mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
Feat/global tables new (#173)
* chore: reworking plugin internals into modules * feat: settings module is now working, added debug module, enabled new api module * chore: restoring external API * feat: refactoring project to use proper inversion of control * feat: plugins can now be loaded in any order * chore: final file migration, all typescript is in modules now * chore: fixing dependencies of cellParser * feat: csv and json views are only registered when they are not colliding with other plugins * feat: fixed library behaviour on canvas * feat: added ability to hide columns from csv files * feat: adding global tables support (wip) * feat: global tables full implementation * feat: added changeset * chore: fixing typechecks
This commit is contained in:
parent
7095130ea5
commit
2bfd2060d7
26 changed files with 975 additions and 60 deletions
5
.changeset/cool-knives-drop.md
Normal file
5
.changeset/cool-knives-drop.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"sqlseal": minor
|
||||
---
|
||||
|
||||
adding global tables support - you can now define table that will be available in all your files
|
||||
|
|
@ -37,7 +37,7 @@ const wasmPlugin = {
|
|||
loader: 'js',
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Plugin to inject worker code
|
||||
|
|
@ -108,6 +108,9 @@ const context = await esbuild.context({
|
|||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
loader: {
|
||||
'.svg': 'text'
|
||||
},
|
||||
plugins: [
|
||||
wasmPlugin,
|
||||
workerPlugin
|
||||
|
|
|
|||
|
|
@ -96,6 +96,11 @@ export class SqlSealDatabase {
|
|||
return this.db?.getColumns(tableName)
|
||||
}
|
||||
|
||||
async count(tableName: string) {
|
||||
const data = await this.db?.select(`SELECT COUNT(*) as count FROM ${tableName}`, {})
|
||||
return data?.data[0]['count']
|
||||
}
|
||||
|
||||
async addColumns(tableName: string, newColumns: string[]) {
|
||||
return this.db?.addColumns(tableName, newColumns)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,10 +182,11 @@ export class CodeblockProcessor extends MarkdownRenderChild {
|
|||
const canvasViews = this.app.workspace.getLeavesOfType("canvas");
|
||||
|
||||
for (const leaf of canvasViews) {
|
||||
const nodes = JSON.parse(leaf.view.data).nodes
|
||||
const node = nodes.filter(n => n.text).find(n => n.text.contains(this.source))
|
||||
const canvasView = leaf.view as any; // Canvas view has data and file properties not exposed in base View type
|
||||
const nodes = JSON.parse(canvasView.data).nodes
|
||||
const node = nodes.filter((n: any) => n.text).find((n: any) => n.text.contains(this.source))
|
||||
if (node) {
|
||||
this.cachedName = leaf.view.file.path
|
||||
this.cachedName = canvasView.file.path
|
||||
return this.cachedName as string
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
src/modules/globalTables/FileConfig.ts
Normal file
49
src/modules/globalTables/FileConfig.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { TFile, Vault } from "obsidian";
|
||||
|
||||
export class FileConfig<T> {
|
||||
constructor(private path: string, private vault: Vault) {
|
||||
|
||||
}
|
||||
|
||||
private data: T[] = []
|
||||
private isLoaded: boolean = false
|
||||
private fileHandler: TFile | null = null
|
||||
|
||||
async load() {
|
||||
this.fileHandler = this.vault.getFileByPath(this.path)
|
||||
if (!this.fileHandler) {
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: add proper ZOD parsing here
|
||||
this.data = JSON.parse(await this.vault.cachedRead(this.fileHandler))
|
||||
this.isLoaded = true
|
||||
}
|
||||
|
||||
async insert(v: T) {
|
||||
if (!this.isLoaded) {
|
||||
await this.load()
|
||||
}
|
||||
this.data.push(v)
|
||||
await this.save()
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this.data
|
||||
}
|
||||
|
||||
async save() {
|
||||
const data = JSON.stringify(this.data)
|
||||
if (!this.fileHandler) {
|
||||
// Creating new file
|
||||
this.fileHandler = await this.vault.create(this.path, data)
|
||||
return
|
||||
}
|
||||
await this.vault.modify(this.fileHandler, data)
|
||||
}
|
||||
|
||||
async remove(v: T) {
|
||||
this.data = this.data.filter(d => d !== v)
|
||||
await this.save()
|
||||
}
|
||||
}
|
||||
206
src/modules/globalTables/GlobalTablesView.ts
Normal file
206
src/modules/globalTables/GlobalTablesView.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
import { IconName, ItemView, Vault, WorkspaceLeaf } from "obsidian";
|
||||
import { MenuBar } from "./GlobalTablesView/MenuBar";
|
||||
import { FileConfig } from "./FileConfig";
|
||||
import { TableConfiguration } from "./modal/NewGlobalTableModal";
|
||||
import { GridRenderer } from "../editor/renderer/GridRenderer";
|
||||
import { createGrid, GridApi, themeQuartz } from "ag-grid-community";
|
||||
import { ConfigCellRenderer } from "./cells/ConfigCell";
|
||||
import { StatsRenderer } from "./cells/StatsRenderer";
|
||||
import { ActionCellRenderer } from "./cells/ActionCell";
|
||||
import { Sync } from "../sync/sync/sync";
|
||||
import { ParserTableDefinition } from "../sync/syncStrategy/types";
|
||||
import { throttle } from "lodash";
|
||||
|
||||
export const GLOBAL_TABLES_VIEW_TYPE = "sqlseal-global-tables";
|
||||
|
||||
const GLOBAL_SOURCE_FILE = "/";
|
||||
|
||||
export class GlobalTablesView extends ItemView {
|
||||
config: FileConfig<TableConfiguration>;
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
vault: Vault,
|
||||
public sync: Sync,
|
||||
) {
|
||||
super(leaf);
|
||||
this.config = new FileConfig("__globalviews.sqlseal", vault);
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
return GLOBAL_TABLES_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return "SQLSeal Global Tables";
|
||||
}
|
||||
|
||||
getIcon(): IconName {
|
||||
return "logo-sqlseal";
|
||||
}
|
||||
|
||||
api: GridApi | null = null;
|
||||
|
||||
async onOpen() {
|
||||
const container = this.containerEl.children[1];
|
||||
container.empty();
|
||||
const c = container.createDiv({ cls: "sqlseal-global-tables-container" });
|
||||
const el = c.createEl("div");
|
||||
|
||||
const menuBar = new MenuBar(el, this.app);
|
||||
|
||||
await this.config.load();
|
||||
for (const conf of this.gridData) {
|
||||
this.sync.registerTable(this.getTableDefinition(conf));
|
||||
}
|
||||
|
||||
menuBar.events.on("new-table", (data) => {
|
||||
this.addElement(data);
|
||||
});
|
||||
|
||||
// el.createDiv({ text: 'HELLO FROM SQLSEAL GLOBAL TABLES VIEW' })
|
||||
|
||||
const gridEl = c.createDiv({ cls: "sql-seal-csv-viewer" });
|
||||
const myTheme = themeQuartz.withParams({
|
||||
borderRadius: 0,
|
||||
browserColorScheme: "light",
|
||||
headerFontSize: 14,
|
||||
headerRowBorder: false,
|
||||
headerVerticalPaddingScale: 1,
|
||||
rowBorder: false,
|
||||
spacing: 16,
|
||||
wrapperBorder: false,
|
||||
wrapperBorderRadius: 0,
|
||||
});
|
||||
|
||||
this.api = createGrid(gridEl, {
|
||||
theme: myTheme,
|
||||
detailRowAutoHeight: true,
|
||||
paginationAutoPageSize: true,
|
||||
rowData: this.gridData,
|
||||
suppressMoveWhenColumnDragging: true,
|
||||
suppressRowDrag: true,
|
||||
suppressCellFocus: true,
|
||||
suppressMaintainUnsortedOrder: true,
|
||||
suppressDragLeaveHidesColumns: true,
|
||||
suppressMoveWhenRowDragging: true,
|
||||
defaultColDef: {
|
||||
resizable: false,
|
||||
sortable: false,
|
||||
rowDrag: false,
|
||||
suppressMovable: true,
|
||||
flex: 1,
|
||||
},
|
||||
autoSizeStrategy: {
|
||||
type: "fitGridWidth",
|
||||
},
|
||||
columnDefs: [
|
||||
{ field: "name" },
|
||||
{ field: "config.type", minWidth: 50 },
|
||||
{ field: "config", cellRenderer: ConfigCellRenderer },
|
||||
{
|
||||
field: "name",
|
||||
cellRenderer: StatsRenderer,
|
||||
headerName: "Stats",
|
||||
minWidth: 200,
|
||||
},
|
||||
{ cellRenderer: ActionCellRenderer, headerName: "Actions" },
|
||||
],
|
||||
context: this,
|
||||
});
|
||||
|
||||
this.setupResizeObserver(gridEl);
|
||||
}
|
||||
|
||||
// Vibe Coded.
|
||||
private smartResize() {
|
||||
if (!this.api) return;
|
||||
|
||||
const api = this.api
|
||||
const columnApi = this.api
|
||||
const containerWidth = this.containerEl.clientWidth;
|
||||
|
||||
// First, auto-size all columns based on content
|
||||
const allColumnIds = columnApi.getColumns()?.map(col => col.getColId()) || [];
|
||||
api.autoSizeColumns(allColumnIds);
|
||||
|
||||
// Calculate total width needed
|
||||
const totalContentWidth = columnApi.getColumns()
|
||||
?.reduce((sum, col) => sum + col.getActualWidth(), 0) || 0;
|
||||
|
||||
// If content fits in container, optionally expand to fill
|
||||
if (totalContentWidth < containerWidth * 0.8) {
|
||||
// Content fits comfortably, you can choose to:
|
||||
// Option A: Leave as-is (content-based sizing)
|
||||
// Option B: Expand to fill container
|
||||
api.sizeColumnsToFit();
|
||||
}
|
||||
// If content is wider than container, keep auto-sized widths
|
||||
// This will enable horizontal scrolling automatically
|
||||
}
|
||||
|
||||
get gridData() {
|
||||
return this.config.items;
|
||||
}
|
||||
|
||||
async addElement(data: TableConfiguration) {
|
||||
await this.config.insert(data);
|
||||
switch (data.config.type) {
|
||||
case "csv":
|
||||
case "json":
|
||||
await this.sync.registerTable(this.getTableDefinition(data));
|
||||
break;
|
||||
case "md-table":
|
||||
console.log("NOT IMPLEMENTED YET", data);
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
getTableDefinition(data: TableConfiguration): ParserTableDefinition {
|
||||
switch (data.config.type) {
|
||||
case "csv":
|
||||
return {
|
||||
tableAlias: data.name,
|
||||
sourceFile: GLOBAL_SOURCE_FILE,
|
||||
type: "file",
|
||||
arguments: [data.config.filename],
|
||||
};
|
||||
case 'json':
|
||||
return {
|
||||
tableAlias: data.name,
|
||||
sourceFile: GLOBAL_SOURCE_FILE,
|
||||
type: "file",
|
||||
arguments: [data.config.filename, data.config.xpath ? data.config.xpath : '$'],
|
||||
}
|
||||
case "md-table":
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
async deleteElement(e: TableConfiguration) {
|
||||
await this.config.remove(e);
|
||||
const def = this.getTableDefinition(e);
|
||||
await this.sync.unregisterTable(def);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.api?.setGridOption("rowData", this.gridData);
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
if (this.api) {
|
||||
this.api.destroy();
|
||||
}
|
||||
}
|
||||
private resizeObserver: ResizeObserver;
|
||||
|
||||
private setupResizeObserver(gridContainer: HTMLElement) {
|
||||
const fn = throttle(() => this.smartResize(), 100)
|
||||
this.resizeObserver = new ResizeObserver(fn);
|
||||
|
||||
this.resizeObserver.observe(gridContainer);
|
||||
}
|
||||
}
|
||||
39
src/modules/globalTables/GlobalTablesView/MenuBar.ts
Normal file
39
src/modules/globalTables/GlobalTablesView/MenuBar.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { args, BusBuilder } from "@hypersphere/omnibus"
|
||||
import { App, ButtonComponent } from "obsidian"
|
||||
import { NewGlobalTableModal, TableConfiguration } from "../modal/NewGlobalTableModal"
|
||||
|
||||
export class MenuBar {
|
||||
|
||||
private bus =(new BusBuilder())
|
||||
.register('new-table', args<TableConfiguration>())
|
||||
.build()
|
||||
|
||||
constructor(private el: HTMLDivElement, private app: App) {
|
||||
this.show()
|
||||
}
|
||||
|
||||
get events() {
|
||||
return this.bus.getRegistrator()
|
||||
}
|
||||
|
||||
openNewTableModal() {
|
||||
const modal = new NewGlobalTableModal(this.app)
|
||||
modal.open()
|
||||
modal.bus.on('data', d => this.bus.trigger('new-table', d))
|
||||
}
|
||||
|
||||
private show() {
|
||||
this.el.empty()
|
||||
this.addButton('Create New Table', () => this.openNewTableModal(), true)
|
||||
}
|
||||
|
||||
private addButton(text: string, onClick: () => void, cta: boolean = false) {
|
||||
const btn = new ButtonComponent(this.el)
|
||||
.setButtonText(text)
|
||||
.onClick(onClick)
|
||||
|
||||
if (cta) {
|
||||
btn.setCta()
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/modules/globalTables/GlobalTablesViewRegister.ts
Normal file
46
src/modules/globalTables/GlobalTablesViewRegister.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { GlobalTablesModule } from "./module";
|
||||
import { addIcon, App, Plugin, WorkspaceLeaf } from "obsidian";
|
||||
import { GLOBAL_TABLES_VIEW_TYPE, GlobalTablesView } from "./GlobalTablesView";
|
||||
// @ts-ignore: Handled by esbuild
|
||||
import SQLSealIcon from './sqlseal-bw.svg'
|
||||
import { Sync } from "../sync/sync/sync";
|
||||
|
||||
|
||||
@(makeInjector<GlobalTablesModule, 'factory'>()(
|
||||
['plugin', 'app', 'sync']
|
||||
))
|
||||
export class GlobalTablesViewRegister {
|
||||
make(plugin: Plugin, app: App, sync: Sync) {
|
||||
|
||||
const activateView = async () => {
|
||||
const { workspace } = plugin.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
const leaves = workspace.getLeavesOfType(GLOBAL_TABLES_VIEW_TYPE);
|
||||
|
||||
if (leaves.length > 0) {
|
||||
// A leaf with our view already exists, use that
|
||||
leaf = leaves[0];
|
||||
} else {
|
||||
leaf = workspace.getLeaf('tab')
|
||||
if (!leaf) { return }
|
||||
await leaf.setViewState({ type: GLOBAL_TABLES_VIEW_TYPE, active: true });
|
||||
}
|
||||
|
||||
// "Reveal" the leaf in case it is in a collapsed sidebar
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
return () => {
|
||||
plugin.registerView(GLOBAL_TABLES_VIEW_TYPE, leaf => new GlobalTablesView(leaf, app.vault, sync))
|
||||
|
||||
|
||||
// SQLSeal Icon
|
||||
addIcon('logo-sqlseal', SQLSealIcon)
|
||||
|
||||
plugin.addRibbonIcon('logo-sqlseal', 'SQLSeal Global Tables', activateView)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/modules/globalTables/InitFactory.ts
Normal file
11
src/modules/globalTables/InitFactory.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { makeInjector } from "@hypersphere/dity"
|
||||
import { GlobalTablesModule } from "./module"
|
||||
|
||||
@(makeInjector<GlobalTablesModule, 'factory'>()(['globalTablesViewRegister']))
|
||||
export class InitFactory {
|
||||
make(register: () => void) {
|
||||
return () => {
|
||||
register()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { args, BusBuilder } from "@hypersphere/omnibus"
|
||||
import { TFile, Vault } from "obsidian"
|
||||
|
||||
interface DropdownValue {
|
||||
name: string
|
||||
path: string
|
||||
originalFile: TFile
|
||||
}
|
||||
|
||||
export class AutocompleteInput {
|
||||
constructor(parent: HTMLElement, private vault: Vault, private extensions: string[]) {
|
||||
const c = parent.createDiv({ cls: 'sqlseal-autocomplete-container'})
|
||||
this.selectedEl = c.createEl('div', { cls: 'selected-el'})
|
||||
this.selectedElText = this.selectedEl.createSpan()
|
||||
this.selectedEl.createEl('button', { text: 'X'}).addEventListener('click', () => {
|
||||
this.input.show()
|
||||
this.input.value = ''
|
||||
this.selectedEl.hide()
|
||||
this.bus.trigger('change', '')
|
||||
})
|
||||
this.selectedEl.hide()
|
||||
this.input = c.createEl('input', { type: 'text' })
|
||||
this.dropdownContainer = c.createDiv({ cls: 'sqlseal-autocomplete-dropdown'})
|
||||
this.render()
|
||||
}
|
||||
|
||||
bus = new BusBuilder()
|
||||
.register('item-selected', args<DropdownValue>())
|
||||
.register('change', args<string>())
|
||||
.build()
|
||||
|
||||
dropdownContainer: HTMLDivElement
|
||||
input: HTMLInputElement
|
||||
dropdownValues: DropdownValue[] = []
|
||||
selectedEl: HTMLDivElement
|
||||
selectedElText: HTMLSpanElement
|
||||
|
||||
render() {
|
||||
this.input.addEventListener('keydown', e => {
|
||||
requestAnimationFrame(() => {
|
||||
this.dropdownValues = this.getFilesForInput((e.target! as any).value)
|
||||
this.setDropdownValues()
|
||||
})
|
||||
})
|
||||
|
||||
this.bus.on('item-selected', e => {
|
||||
this.input.value = e.originalFile.path
|
||||
this.bus.trigger('change', e.originalFile.path)
|
||||
this.dropdownValues = []
|
||||
this.setDropdownValues()
|
||||
|
||||
this.selectedEl.show()
|
||||
this.selectedElText.textContent = e.originalFile.path
|
||||
this.input.hide()
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
setDropdownValues() {
|
||||
this.dropdownContainer.empty()
|
||||
for (const val of this.dropdownValues) {
|
||||
this.dropdownContainer.appendChild(this.createDropdownOption(val))
|
||||
}
|
||||
}
|
||||
|
||||
private createDropdownOption(opt: DropdownValue) {
|
||||
const el = createEl('div', { cls: 'sqlseal-dropdown-el' })
|
||||
el.createDiv({ text: opt.name, cls: 'sqlseal-dropdown-el-name' })
|
||||
el.createDiv({ text: opt.path, cls: 'sqlseal-dropdown-el-path' })
|
||||
el.addEventListener('click', e => {
|
||||
this.bus.trigger('item-selected', opt)
|
||||
})
|
||||
return el
|
||||
}
|
||||
|
||||
getFilesForInput(inp: string): DropdownValue[] {
|
||||
return this.vault.getFiles()
|
||||
.filter(f => this.extensions.includes(f.extension))
|
||||
.filter(f => f.path.includes(inp))
|
||||
.map(f => ({ name: f.name, path: f.parent?.path ?? '', originalFile: f }))
|
||||
}
|
||||
}
|
||||
28
src/modules/globalTables/cells/ActionCell.ts
Normal file
28
src/modules/globalTables/cells/ActionCell.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import type { ICellRendererComp, ICellRendererParams } from "ag-grid-community";
|
||||
import { TableConfiguration } from "../modal/NewGlobalTableModal";
|
||||
import { ButtonComponent } from "obsidian";
|
||||
import { GlobalTablesView } from "../GlobalTablesView";
|
||||
|
||||
export class ActionCellRenderer implements ICellRendererComp {
|
||||
private eGui!: HTMLDivElement;
|
||||
|
||||
public init(params: ICellRendererParams<any, string, GlobalTablesView>): void {
|
||||
const { value, data, context } = params;
|
||||
|
||||
|
||||
|
||||
this.eGui = document.createElement("div");
|
||||
|
||||
new ButtonComponent(this.eGui)
|
||||
.setIcon('trash-2')
|
||||
.onClick(() => context.deleteElement(data))
|
||||
}
|
||||
|
||||
public getGui(): HTMLElement {
|
||||
return this.eGui;
|
||||
}
|
||||
|
||||
public refresh(params: ICellRendererParams): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
47
src/modules/globalTables/cells/ConfigCell.ts
Normal file
47
src/modules/globalTables/cells/ConfigCell.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import type { ICellRendererComp, ICellRendererParams } from "ag-grid-community";
|
||||
import { TableConfiguration } from "../modal/NewGlobalTableModal";
|
||||
import { Component, MarkdownRenderer, TFile } from "obsidian";
|
||||
import { GlobalTablesView } from "../GlobalTablesView";
|
||||
|
||||
export class ConfigCellRenderer implements ICellRendererComp {
|
||||
private eGui!: HTMLDivElement;
|
||||
|
||||
public init(
|
||||
params: ICellRendererParams<
|
||||
TableConfiguration,
|
||||
TableConfiguration["config"],
|
||||
GlobalTablesView
|
||||
>,
|
||||
): void {
|
||||
const { value, data, context } = params;
|
||||
|
||||
this.eGui = document.createElement("div");
|
||||
this.eGui.className = "config";
|
||||
|
||||
const container = this.eGui.createDiv();
|
||||
|
||||
if (value?.type === "csv") {
|
||||
const link = container.createEl("a");
|
||||
link.href = value.filename;
|
||||
link.textContent = value.filename;
|
||||
link.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const file = context.app.vault.getAbstractFileByPath(value.filename);
|
||||
if (file instanceof TFile) {
|
||||
context.app.workspace.openLinkText(value.filename, "", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (value?.type === 'json') {
|
||||
container.textContent = value.filename + '#' + (value.xpath ? value.xpath : '$')
|
||||
}
|
||||
}
|
||||
|
||||
public getGui(): HTMLElement {
|
||||
return this.eGui;
|
||||
}
|
||||
|
||||
public refresh(params: ICellRendererParams): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
59
src/modules/globalTables/cells/StatsRenderer.ts
Normal file
59
src/modules/globalTables/cells/StatsRenderer.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import type { ICellRendererComp, ICellRendererParams } from "ag-grid-community";
|
||||
import { TableConfiguration } from "../modal/NewGlobalTableModal";
|
||||
import { GlobalTablesView } from "../GlobalTablesView";
|
||||
import { OmnibusRegistrator } from "@hypersphere/omnibus";
|
||||
|
||||
export class StatsRenderer implements ICellRendererComp {
|
||||
private eGui!: HTMLDivElement;
|
||||
|
||||
context: GlobalTablesView
|
||||
data: TableConfiguration
|
||||
eventName: string = ''
|
||||
|
||||
reg: OmnibusRegistrator
|
||||
|
||||
syncFn = () => {
|
||||
this.sync()
|
||||
}
|
||||
|
||||
public init(params: ICellRendererParams<TableConfiguration, string, GlobalTablesView>): void {
|
||||
const { value, data, context } = params;
|
||||
this.context = context
|
||||
this.data = data!
|
||||
|
||||
this.eGui = document.createElement("div");
|
||||
this.eGui.className = "stats";
|
||||
|
||||
const stats = this.eGui.createSpan()
|
||||
stats.textContent = 'Loading'
|
||||
|
||||
requestAnimationFrame(async () => { this.sync() })
|
||||
|
||||
// Watching for the changes
|
||||
this.setupWatchersAsync(context, data!)
|
||||
|
||||
}
|
||||
|
||||
async setupWatchersAsync(context: GlobalTablesView, data: TableConfiguration) {
|
||||
this.reg = context.sync.getRegistrator()
|
||||
this.eventName = await context.sync.getEventNameForAlias('/', data!.name)
|
||||
this.reg.on(this.eventName, this.syncFn)
|
||||
}
|
||||
|
||||
async sync() {
|
||||
const result = await this.context.sync.getStats('/', this.data!.name)
|
||||
this.eGui.textContent = `Rows: ${result.rows} / Columns: ${result.columns}`
|
||||
}
|
||||
|
||||
public getGui(): HTMLElement {
|
||||
return this.eGui;
|
||||
}
|
||||
|
||||
public refresh(params: ICellRendererParams): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.reg.off(this.eventName, this.syncFn)
|
||||
}
|
||||
}
|
||||
133
src/modules/globalTables/modal/NewGlobalTableModal.ts
Normal file
133
src/modules/globalTables/modal/NewGlobalTableModal.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import { MenuSeparator, Modal, Setting } from "obsidian";
|
||||
import { AutocompleteInput } from "../autocompleteElement/AutocompleteInput";
|
||||
import { args, BusBuilder } from "@hypersphere/omnibus";
|
||||
|
||||
export interface CSVConfig {
|
||||
type: 'csv'
|
||||
filename: string
|
||||
}
|
||||
|
||||
export interface JsonConfig {
|
||||
type: 'json'
|
||||
filename: string
|
||||
xpath: string
|
||||
}
|
||||
|
||||
export interface MDTable {
|
||||
type: 'md-table'
|
||||
filename: string
|
||||
selector: string
|
||||
}
|
||||
|
||||
export interface TableConfiguration {
|
||||
name: string
|
||||
config: CSVConfig | JsonConfig | MDTable
|
||||
}
|
||||
|
||||
export type Type = TableConfiguration['config']['type']
|
||||
|
||||
export class NewGlobalTableModal extends Modal {
|
||||
|
||||
bus = new BusBuilder()
|
||||
.register('data', args<TableConfiguration>())
|
||||
.build()
|
||||
|
||||
data: TableConfiguration = {
|
||||
name: "",
|
||||
config: {
|
||||
type: 'csv',
|
||||
filename: ''
|
||||
}
|
||||
};
|
||||
|
||||
typeSectionEl: HTMLElement | null = null
|
||||
|
||||
onOpen(): void {
|
||||
const { contentEl } = this;
|
||||
contentEl.closest('.modal')?.classList.add('modal-overflow')
|
||||
contentEl.createEl("h2", { text: "New Table Modal" });
|
||||
|
||||
const settingsEl = contentEl.createDiv();
|
||||
|
||||
const name = new Setting(settingsEl).setName("Table Name").addText((txt) =>
|
||||
txt.setPlaceholder("eg. Data").onChange((val) => {
|
||||
this.data.name = val;
|
||||
}),
|
||||
);
|
||||
|
||||
const type = new Setting(settingsEl)
|
||||
.setName("Source Type")
|
||||
.addDropdown((drop) =>
|
||||
drop.addOptions({
|
||||
csv: "CSV",
|
||||
json: "JSON",
|
||||
// "md-table": "Markdown Table",
|
||||
})
|
||||
.setValue(this.data.config.type)
|
||||
.onChange(v => {
|
||||
this.data.config.type = v as Type
|
||||
this.renderTypeSection()
|
||||
})
|
||||
);
|
||||
|
||||
this.typeSectionEl = contentEl.createDiv()
|
||||
this.renderTypeSection()
|
||||
|
||||
new Setting(contentEl)
|
||||
.addButton(b => b
|
||||
.setButtonText('Add')
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.bus.trigger('data', this.data)
|
||||
this.close()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
renderTypeSection() {
|
||||
if (!this.typeSectionEl) {
|
||||
return
|
||||
}
|
||||
const el = this.typeSectionEl
|
||||
el.empty()
|
||||
|
||||
switch (this.data.config.type) {
|
||||
case 'csv':
|
||||
this.renderCsvSection()
|
||||
break
|
||||
case 'json':
|
||||
this.renderJsonSection()
|
||||
break
|
||||
case 'md-table':
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
renderCsvSection() {
|
||||
const filename = new Setting(this.typeSectionEl!)
|
||||
.setName('Filename')
|
||||
const autocomplete = new AutocompleteInput(filename.controlEl, this.app.vault, ['csv'])
|
||||
autocomplete.bus.on('change', (path) => {
|
||||
this.data.config.filename = path
|
||||
})
|
||||
}
|
||||
|
||||
renderJsonSection() {
|
||||
const filename = new Setting(this.typeSectionEl!)
|
||||
.setName('Filename')
|
||||
const autocomplete = new AutocompleteInput(filename.controlEl, this.app.vault, ['json', 'json5'])
|
||||
autocomplete.bus.on('change', (path) => {
|
||||
this.data.config.filename = path
|
||||
})
|
||||
|
||||
const xpath = new Setting(this.typeSectionEl!)
|
||||
.setName('Selector')
|
||||
.addText(c => c.setPlaceholder('$.[0]')
|
||||
.onChange(e => {
|
||||
(this.data.config as JsonConfig).xpath = e
|
||||
}))
|
||||
|
||||
}
|
||||
}
|
||||
20
src/modules/globalTables/module.ts
Normal file
20
src/modules/globalTables/module.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { asFactory, buildContainer } from "@hypersphere/dity";
|
||||
import { InitFactory } from "./InitFactory";
|
||||
import { App, Plugin } from "obsidian";
|
||||
import { GlobalTablesViewRegister } from "./GlobalTablesViewRegister";
|
||||
import { Sync } from "../sync/sync/sync";
|
||||
|
||||
export const globalTables = buildContainer(c =>
|
||||
c.register({
|
||||
init: asFactory(InitFactory),
|
||||
globalTablesViewRegister: asFactory(GlobalTablesViewRegister)
|
||||
})
|
||||
.externals<{
|
||||
plugin: Plugin,
|
||||
app: App,
|
||||
sync: Sync
|
||||
}>()
|
||||
.exports('init')
|
||||
)
|
||||
|
||||
export type GlobalTablesModule = typeof globalTables
|
||||
1
src/modules/globalTables/sqlseal-bw.svg
Normal file
1
src/modules/globalTables/sqlseal-bw.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 91 91" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;"><g><g><g><path d="M76.595,73.106c-1.409,-1.318 -2.846,-2.451 -3.7,-3.097c0.15,-2.473 -0.239,-4.9 -1.171,-7.268c-1.962,-4.986 -5.948,-8.893 -9.201,-11.739c-4.354,-3.81 -3.922,-9.686 -3.276,-12.851c0.361,-1.785 -0.072,-3.6 -1.135,-4.999c-0.904,-1.189 -5.401,-2.873 -8.602,-1.632c-5.254,2.037 -8.97,4.302 -10.802,7.457c-1.335,2.299 -1.688,4.865 -1.05,7.628c0.195,0.848 0.762,1.934 1.545,3.438c1.646,3.157 4.133,7.928 3.103,10.994c-0.354,1.055 -1.136,1.857 -2.391,2.451c-4.534,2.147 -6.793,-0.153 -9.977,-4.196c-1.169,-1.486 -2.275,-2.889 -3.594,-3.743c-1.806,-1.168 -4.117,-1.465 -6.683,-0.861c-1.208,0.284 -2.147,1.209 -2.45,2.413c-0.298,1.179 0.08,2.409 0.986,3.212c0.04,0.036 0.076,0.071 0.103,0.096c0.559,0.548 0.44,1.205 0.363,1.462c-0.172,0.576 -0.623,0.988 -1.205,1.102c-1.537,0.302 -3.123,0.813 -4.715,1.521c-1.778,0.794 -2.898,2.57 -2.852,4.523c0.045,1.958 1.2,3.616 3.012,4.328c2.315,0.908 5.277,1.412 8.804,1.496c1.952,0.047 3.731,0.966 4.882,2.521c0.407,0.55 0.842,1.085 1.296,1.604c-0.364,0.432 -0.745,0.869 -1.145,1.312c-1.141,1.263 -1.381,3.706 -0.875,4.627c2.044,3.717 13.868,2.571 16.224,0.599c0.113,-0.096 0.216,1.548 3.184,2.28c6.373,1.573 19.837,0.086 22.118,-5.228c1.11,0.221 3.056,0.571 5.076,0.749c0.749,0.067 1.563,0.119 2.38,0.119c2.589,-0 5.202,-0.522 5.841,-2.762c0.574,-2.005 -0.727,-4.407 -4.093,-7.556Zm-9.694,4.685c-2.685,4.59 -8.859,7.267 -15.731,7.457l-1.498,0c-1.877,-0.046 -3.789,-0.272 -5.684,-0.698c4.033,-2.439 6.299,-5.342 6.47,-5.565c0.562,-0.736 0.421,-1.788 -0.315,-2.35c-0.736,-0.562 -1.788,-0.421 -2.35,0.315c-0.067,0.087 -6.82,8.751 -17.855,7.5c-0.624,-0.071 -0.863,-0.534 -0.937,-0.73c-0.076,-0.2 -0.205,-0.716 0.226,-1.194c2.965,-3.281 4.995,-6.26 6.033,-8.854c0.344,-0.859 -0.074,-1.835 -0.934,-2.179c-0.86,-0.344 -1.835,0.074 -2.179,0.934c-0.459,1.147 -1.192,2.439 -2.166,3.829c-0.239,-0.291 -0.474,-0.586 -0.697,-0.888c-1.771,-2.393 -4.504,-3.807 -7.497,-3.879c-3.09,-0.073 -5.738,-0.511 -7.659,-1.265c-0.78,-0.306 -0.879,-1 -0.886,-1.284c-0.006,-0.232 0.041,-1.016 0.865,-1.384c1.36,-0.604 2.705,-1.039 3.995,-1.292c1.799,-0.353 3.245,-1.667 3.773,-3.432c0.515,-1.724 0.044,-3.57 -1.23,-4.818c-0.005,-0.005 -0.013,-0.012 -0.017,-0.017c-0.026,-0.024 -0.052,-0.049 -0.078,-0.074c1.626,-0.361 2.962,-0.213 3.972,0.441c0.851,0.55 1.789,1.741 2.782,3.002c2.695,3.424 6.77,8.598 14.045,5.152c2.093,-0.992 3.484,-2.477 4.135,-4.414c1.484,-4.418 -1.4,-9.951 -3.308,-13.611c-0.54,-1.036 -1.153,-2.211 -1.252,-2.642c-0.446,-1.934 -0.223,-3.631 0.682,-5.191c1.6,-2.754 5.234,-4.952 10.803,-6.53c0.882,-0.249 1.837,-0.042 2.548,0.551l0.049,0.041c0.799,0.665 1.165,1.723 0.954,2.76c-0.79,3.877 -1.276,11.116 4.354,16.042c2.968,2.596 6.592,6.13 8.289,10.444c1.728,4.389 1.171,8.911 -1.702,13.823Zm5.243,2.115c-0.85,-0.091 -1.677,-0.211 -2.404,-0.331c0.018,-0.03 0.037,-0.06 0.055,-0.091c1.124,-1.921 1.944,-3.827 2.463,-5.71c0.598,0.488 1.266,1.057 1.926,1.669c2.889,2.675 3.235,3.925 3.276,4.226c-0.275,0.16 -1.464,0.65 -5.316,0.237Z" style="fill-rule:nonzero;"/></g></g><g><g><circle cx="47.242" cy="39.859" r="1.676"/></g></g><g><g><path d="M76.42,14.232c-1.116,3.3 -7.37,4.166 -13.969,1.935c-6.598,-2.231 -11.044,-6.715 -9.928,-10.014c1.115,-3.3 7.369,-4.166 13.968,-1.935c6.599,2.231 11.044,6.715 9.929,10.014Z" style="fill:none;fill-rule:nonzero;stroke:#000;stroke-width:3.15px;"/><path d="M73.895,21.7c-1.116,3.299 -7.369,4.165 -13.968,1.934c-6.599,-2.231 -11.044,-6.714 -9.929,-10.014" style="fill:none;fill-rule:nonzero;stroke:#000;stroke-width:3.15px;"/><path d="M52.523,6.153l-5.05,14.935c-1.115,3.3 3.33,7.783 9.929,10.014c6.599,2.231 12.853,1.365 13.968,-1.934l5.05,-14.936" style="fill:none;fill-rule:nonzero;stroke:#000;stroke-width:3.15px;"/></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4 KiB |
|
|
@ -11,7 +11,8 @@ type InitFn = () => void
|
|||
'contextMenu.init',
|
||||
'sync.init',
|
||||
'debug.init',
|
||||
'api.init'
|
||||
'api.init',
|
||||
'globalTables.init'
|
||||
]))
|
||||
export class Init {
|
||||
async make(
|
||||
|
|
@ -21,7 +22,8 @@ export class Init {
|
|||
contextMenu: InitFn,
|
||||
syncInit: InitFn,
|
||||
debugInit: InitFn,
|
||||
apiInit: InitFn
|
||||
apiInit: InitFn,
|
||||
globalTablesInit: InitFn
|
||||
) {
|
||||
return () => {
|
||||
settingsInit()
|
||||
|
|
@ -29,8 +31,9 @@ export class Init {
|
|||
highlighInit()
|
||||
contextMenu()
|
||||
syncInit()
|
||||
debugInit()
|
||||
// debugInit()
|
||||
apiInit()
|
||||
globalTablesInit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import { syntaxHighlight } from '../syntaxHighlight/module'
|
|||
import { contextMenu } from '../contextMenu/module'
|
||||
import { debugModule } from '../debug/module'
|
||||
import { apiModule } from '../api/module'
|
||||
import { globalTables } from '../globalTables/module'
|
||||
|
||||
const obsidian = buildContainer(c => c
|
||||
.externals<{
|
||||
|
|
@ -30,7 +31,8 @@ export const mainModule = buildContainer(c => c
|
|||
syntaxHighlight,
|
||||
contextMenu,
|
||||
debug: debugModule,
|
||||
api: apiModule
|
||||
api: apiModule,
|
||||
globalTables
|
||||
})
|
||||
.register({
|
||||
init: asFactory(Init)
|
||||
|
|
@ -77,6 +79,11 @@ export const mainModule = buildContainer(c => c
|
|||
'api.db': 'db.db',
|
||||
'api.rendererRegistry': 'editor.rendererRegistry'
|
||||
})
|
||||
.resolve({
|
||||
'globalTables.plugin': 'obsidian.plugin',
|
||||
'globalTables.app': 'obsidian.app',
|
||||
'globalTables.sync': 'sync.syncBus'
|
||||
})
|
||||
)
|
||||
|
||||
export type MainModule = typeof mainModule
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { WorkspaceLeaf, TextFileView, Menu, MenuItem } from "obsidian";
|
||||
import { WorkspaceLeaf, TextFileView, Menu, MenuItem, IconName } from "obsidian";
|
||||
import { parse, unparse } from "papaparse";
|
||||
import {
|
||||
GridRenderer,
|
||||
|
|
@ -12,7 +12,8 @@ import { RenameColumnModal } from "../modal/renameColumnModal";
|
|||
import { CodeSampleModal } from "../modal/showCodeSample";
|
||||
import { DeleteConfirmationModal } from "../modal/deleteConfirmationModal";
|
||||
import { CSVColumnContextMenu } from "../menu/csvColumnContextMenu";
|
||||
import { AgColumn } from "ag-grid-community";
|
||||
import { AgColumn, Column } from "ag-grid-community";
|
||||
import { CSVViewMenuBar } from "./CSVViewMenuBar";
|
||||
|
||||
const delay = (n: number) => new Promise((resolve) => setTimeout(resolve, n));
|
||||
|
||||
|
|
@ -236,6 +237,10 @@ export class CSVView extends TextFileView {
|
|||
}
|
||||
}
|
||||
|
||||
getIcon(): IconName {
|
||||
return 'table'
|
||||
}
|
||||
|
||||
moveColumn(name: string, toIndex: number) {
|
||||
let fields = this.result.fields as Array<string>;
|
||||
fields = fields.filter((f) => f !== name);
|
||||
|
|
@ -307,6 +312,38 @@ export class CSVView extends TextFileView {
|
|||
|
||||
showHidden: boolean = false;
|
||||
|
||||
private menu(container: HTMLElement) {
|
||||
|
||||
const buttonsRow = container.createDiv({
|
||||
cls: "sql-seal-csv-viewer-buttons",
|
||||
});
|
||||
|
||||
const menuBar = new CSVViewMenuBar(buttonsRow, this.settings, this.app)
|
||||
|
||||
const events = menuBar.events
|
||||
|
||||
events.on('add-column', (columnName: string) => {
|
||||
this.addNewColumn(columnName)
|
||||
})
|
||||
|
||||
events.on('add-row', () => {
|
||||
this.createRow();
|
||||
})
|
||||
|
||||
events.on('generate-code', () => {
|
||||
if (!this.file) {
|
||||
return;
|
||||
}
|
||||
const modal = new CodeSampleModal(this.app, this.file);
|
||||
modal.open();
|
||||
})
|
||||
|
||||
events.on('toggle-hidden', (v) => {
|
||||
this.showHidden = v
|
||||
this.refreshTypes();
|
||||
})
|
||||
}
|
||||
|
||||
private async renderCSV() {
|
||||
if (this.isLoading) {
|
||||
if (this.api) {
|
||||
|
|
@ -320,57 +357,12 @@ export class CSVView extends TextFileView {
|
|||
const csvEditorDiv = this.contentEl.createDiv({
|
||||
cls: "sql-seal-csv-editor",
|
||||
});
|
||||
|
||||
const buttonsRow = csvEditorDiv.createDiv({
|
||||
cls: "sql-seal-csv-viewer-buttons",
|
||||
});
|
||||
await this.loadConfig();
|
||||
if (this.settings.get("enableEditing")) {
|
||||
const createColumn = buttonsRow.createEl("button", {
|
||||
text: "Add Column",
|
||||
});
|
||||
const createRow = buttonsRow.createEl("button", { text: "Add Row" });
|
||||
|
||||
createColumn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const modal = new RenameColumnModal(this.app, (res) => {
|
||||
this.addNewColumn(res);
|
||||
});
|
||||
modal.open();
|
||||
});
|
||||
|
||||
createRow.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.createRow();
|
||||
});
|
||||
}
|
||||
const generateSqlCode = buttonsRow.createEl("button", {
|
||||
text: "Generate SQLSeal code",
|
||||
});
|
||||
|
||||
|
||||
|
||||
const showHidden = buttonsRow.createEl('button', {
|
||||
text: 'Show Hidden Columns'
|
||||
})
|
||||
|
||||
showHidden.addEventListener('click', () => {
|
||||
this.showHidden = !this.showHidden
|
||||
showHidden.textContent = this.showHidden ? 'Hide Hidden Columns' : 'Show Hidden Columns'
|
||||
this.refreshTypes()
|
||||
})
|
||||
this.menu(csvEditorDiv)
|
||||
|
||||
const gridEl = csvEditorDiv.createDiv({ cls: "sql-seal-csv-viewer" });
|
||||
|
||||
generateSqlCode.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (!this.file) {
|
||||
return;
|
||||
}
|
||||
const modal = new CodeSampleModal(this.app, this.file);
|
||||
modal.open();
|
||||
});
|
||||
|
||||
const grid = new GridRenderer(this.settings, null, this.app);
|
||||
const csvView = this;
|
||||
const data = this.prepareData();
|
||||
|
|
|
|||
64
src/modules/settings/view/CSVViewMenuBar.ts
Normal file
64
src/modules/settings/view/CSVViewMenuBar.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { App, ButtonComponent } from "obsidian";
|
||||
import { RenameColumnModal } from "../modal/renameColumnModal";
|
||||
import { Settings } from "../Settings";
|
||||
import { args, BusBuilder } from "@hypersphere/omnibus";
|
||||
|
||||
const SHOW_HIDDEN_TEXT = "Show Hidden Columns";
|
||||
const HIDE_HIDDEN_TEXT = "Hide Hidden Columns";
|
||||
|
||||
export class CSVViewMenuBar {
|
||||
private bus = new BusBuilder()
|
||||
.register("add-row", args<[]>())
|
||||
.register("add-column", args<string>())
|
||||
.register("generate-code", args<[]>())
|
||||
.register("toggle-hidden", args<boolean>())
|
||||
.build();
|
||||
|
||||
constructor(
|
||||
private el: HTMLElement,
|
||||
private settings: Settings,
|
||||
private app: App,
|
||||
) {
|
||||
this.show();
|
||||
}
|
||||
|
||||
private showHidden: boolean = false;
|
||||
|
||||
get events() {
|
||||
return this.bus.getRegistrator();
|
||||
}
|
||||
|
||||
private show() {
|
||||
const el = this.el;
|
||||
|
||||
el.empty();
|
||||
|
||||
if (this.settings.get("enableEditing")) {
|
||||
new ButtonComponent(el)
|
||||
.setButtonText("Add Row")
|
||||
.setCta()
|
||||
.onClick(() => this.bus.trigger("add-row"));
|
||||
|
||||
new ButtonComponent(el).setButtonText("Add Column").onClick(() => {
|
||||
const modal = new RenameColumnModal(this.app, (res) => {
|
||||
this.bus.trigger("add-column", res);
|
||||
});
|
||||
modal.open();
|
||||
});
|
||||
|
||||
new ButtonComponent(el)
|
||||
.setButtonText("Generate SQLSeal Code")
|
||||
.onClick(() => this.bus.trigger("generate-code"));
|
||||
|
||||
const showHiddenButton = new ButtonComponent(el)
|
||||
.setButtonText(SHOW_HIDDEN_TEXT)
|
||||
.onClick(() => {
|
||||
this.showHidden = !this.showHidden;
|
||||
showHiddenButton.setButtonText(
|
||||
this.showHidden ? HIDE_HIDDEN_TEXT : SHOW_HIDDEN_TEXT,
|
||||
);
|
||||
this.bus.trigger("toggle-hidden", this.showHidden);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { WorkspaceLeaf, TextFileView, Menu } from 'obsidian';
|
||||
import { WorkspaceLeaf, TextFileView, Menu, IconName } from 'obsidian';
|
||||
import { parse, stringify } from 'json5'
|
||||
|
||||
export const JSON_VIEW_TYPE = "sqlseal-json-viewer";
|
||||
|
|
@ -33,6 +33,10 @@ export class JsonView extends TextFileView {
|
|||
// Cleanup if needed
|
||||
}
|
||||
|
||||
getIcon(): IconName {
|
||||
return 'file-json'
|
||||
}
|
||||
|
||||
async setViewData(data: string, clear: boolean): Promise<void> {
|
||||
this.content = data;
|
||||
await this.renderJson();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,14 @@ export class TableAliasesRepository extends Repository {
|
|||
}
|
||||
|
||||
async deleteMapping(id: string) {
|
||||
this.db.deleteData(this.TABLE_NAME, [{ id: id }], 'id')
|
||||
await this.db.deleteData(this.TABLE_NAME, [{ id: id }], 'id')
|
||||
}
|
||||
|
||||
async deleteMappingByNames(aliasName: string, sourceFileName: string) {
|
||||
const data = await this.getByAlias(sourceFileName, aliasName)
|
||||
if (data) {
|
||||
await this.db.deleteData(this.TABLE_NAME, [{ id: data.id }], 'id')
|
||||
}
|
||||
}
|
||||
|
||||
private async createTable() {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ export class Sync {
|
|||
|
||||
// START SYNCING
|
||||
this.startSync()
|
||||
|
||||
await this.refreshGlobalMappings()
|
||||
}
|
||||
|
||||
async syncFileByName(fileName: string) {
|
||||
|
|
@ -113,6 +115,17 @@ export class Sync {
|
|||
await this.tableDefinitionsRepo.insert(log)
|
||||
}
|
||||
|
||||
private globalTables: Record<string, string> = {}
|
||||
|
||||
async refreshGlobalMappings() {
|
||||
const globalMappings = await this.tableMapLog.getByContext('/') as { alias_name: string, table_name: string }[]
|
||||
this.globalTables = Object.fromEntries(globalMappings.map(g => [g.alias_name, g.table_name]))
|
||||
}
|
||||
|
||||
get globalTablesMapping() {
|
||||
return this.globalTables
|
||||
}
|
||||
|
||||
async getTablesMappingForContext(sourceFileName: string) {
|
||||
const tables = await this.tableMapLog.getByContext(sourceFileName) as { alias_name: string, table_name: string }[]
|
||||
const map = tables.reduce((acc, t) => ({
|
||||
|
|
@ -120,11 +133,14 @@ export class Sync {
|
|||
[t.alias_name as string]: t.table_name
|
||||
}), {})
|
||||
|
||||
// FIXME: adding globals here.
|
||||
|
||||
return {
|
||||
...map,
|
||||
...this.globalTablesMapping,
|
||||
files: 'files',
|
||||
tasks: 'tasks',
|
||||
tags: 'tags'
|
||||
tags: 'tags',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +187,27 @@ export class Sync {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (reg.sourceFile === '/') {
|
||||
await this.refreshGlobalMappings()
|
||||
}
|
||||
}
|
||||
|
||||
unregisterTable(reg: ParserTableDefinition) {
|
||||
return this.tableMapLog.deleteMappingByNames(reg.tableAlias, reg.sourceFile)
|
||||
}
|
||||
|
||||
async getStats(sourceFileName: string, table: string) {
|
||||
const tab = await this.tableMapLog.getByAlias(sourceFileName, table)
|
||||
if (!tab) {
|
||||
return { rows: 0, columns: 0 }
|
||||
}
|
||||
const columns = await this.db.getColumns(tab.table_name)
|
||||
const rows = await this.db.count(tab.table_name)
|
||||
return {
|
||||
rows,
|
||||
columns: columns ? columns.length : 0
|
||||
}
|
||||
}
|
||||
|
||||
getRegistrator() {
|
||||
|
|
|
|||
58
src/styles/autocomplete.scss
Normal file
58
src/styles/autocomplete.scss
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
.sqlseal-autocomplete-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
& input[type="text"] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlseal-autocomplete-dropdown {
|
||||
position: absolute;
|
||||
top: 100%; left: 0;
|
||||
margin-top: 0.5em;
|
||||
right: 0;
|
||||
text-align: left;
|
||||
background: white;
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 3px 5px #CCC;
|
||||
}
|
||||
|
||||
|
||||
.modal-overflow {
|
||||
overflow: visible;
|
||||
// --dialog-width: 1000px;
|
||||
}
|
||||
|
||||
.sqlseal-dropdown-el {
|
||||
padding: 4px 16px 4px 8px;
|
||||
border-top: 1px solid #EEE;
|
||||
overflow: hidden;
|
||||
&:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #a8c4ef;
|
||||
}
|
||||
}
|
||||
|
||||
.sqlseal-dropdown-el-name {
|
||||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.sqlseal-dropdown-el-path {
|
||||
opacity: 0.9;
|
||||
font-size: 0.7em;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
6
src/styles/global-tables.scss
Normal file
6
src/styles/global-tables.scss
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.sqlseal-global-tables-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
|
|
@ -6,4 +6,6 @@
|
|||
@use 'obsidianMinimal';
|
||||
@use 'agGrid';
|
||||
@use 'settings';
|
||||
@use 'canvas';
|
||||
@use 'canvas';
|
||||
@use 'autocomplete';
|
||||
@use 'global-tables';
|
||||
|
|
|
|||
Loading…
Reference in a new issue