mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
Feat/hide csv columns (#176)
* 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
This commit is contained in:
parent
285684dfc4
commit
7095130ea5
7 changed files with 191 additions and 84 deletions
5
.changeset/cool-taxis-boil.md
Normal file
5
.changeset/cool-taxis-boil.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"sqlseal": minor
|
||||
---
|
||||
|
||||
added ability to hide columns from CSV files
|
||||
99
src/modules/settings/menu/csvColumnContextMenu.ts
Normal file
99
src/modules/settings/menu/csvColumnContextMenu.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { Menu, MenuItem } from "obsidian";
|
||||
import { RenameColumnModal } from "../modal/renameColumnModal";
|
||||
import { CSVView } from "../view/CSVView";
|
||||
import { AgColumn } from "ag-grid-community";
|
||||
import { DeleteConfirmationModal } from "../modal/deleteConfirmationModal";
|
||||
import { ColumnType } from "../../../utils/types";
|
||||
|
||||
export class CSVColumnContextMenu {
|
||||
constructor(
|
||||
private csvView: CSVView,
|
||||
private column: AgColumn,
|
||||
) { }
|
||||
|
||||
onRename() {
|
||||
const modal = new RenameColumnModal(this.csvView.app, (res) => {
|
||||
this.csvView.renameColumn(this.columnField, res);
|
||||
});
|
||||
modal.open();
|
||||
}
|
||||
|
||||
onDelete() {
|
||||
const colName = this.columnField;
|
||||
const modal = new DeleteConfirmationModal(
|
||||
this.csvView.app,
|
||||
`column ${colName}`,
|
||||
() => {
|
||||
this.csvView.deleteColumn(colName);
|
||||
},
|
||||
);
|
||||
modal.open();
|
||||
}
|
||||
|
||||
get columnField() {
|
||||
return this.column.getUserProvidedColDef()?.field!;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.csvView.getColumnConfig(this.columnField)
|
||||
}
|
||||
|
||||
show(pos: { x: number; y: number }) {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("Rename Column");
|
||||
item.onClick(this.onRename.bind(this));
|
||||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("Delete Column");
|
||||
item.onClick(this.onDelete.bind(this));
|
||||
});
|
||||
|
||||
menu.addSeparator();
|
||||
menu.addItem(item => {
|
||||
const isHidden = !!this.config.isHidden
|
||||
item.setTitle(isHidden ? 'Show in queries' : 'Hide from queries')
|
||||
item.onClick(async () => {
|
||||
await this.csvView.setConfig(this.columnField, { isHidden: !isHidden })
|
||||
this.csvView.refreshTypes()
|
||||
this.csvView.saveData()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// FIXME: rework it to submenus.
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => {
|
||||
// item.setDisabled(true)
|
||||
item.setTitle("Data Type");
|
||||
// item.setIsLabel(true)
|
||||
const ipfSubmenu = (item as any).setSubmenu();
|
||||
const types = [
|
||||
"auto",
|
||||
"text",
|
||||
"number",
|
||||
"boolean",
|
||||
"date",
|
||||
] as ColumnType[];
|
||||
|
||||
const colName = this.columnField;
|
||||
|
||||
const current = this.csvView.getColumnType(colName);
|
||||
types.forEach((type) => {
|
||||
ipfSubmenu.addItem((subItem: MenuItem) => {
|
||||
const checkbox = type === current ? "✓ " : "";
|
||||
subItem.setTitle(checkbox + type);
|
||||
subItem.onClick(() => {
|
||||
this.csvView.changeColumnType(colName, type);
|
||||
this.csvView.refreshTypes();
|
||||
this.csvView.loadDataIntoGrid();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtPosition(pos);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,14 @@ import {
|
|||
GridRendererCommunicator,
|
||||
} from "../../editor/renderer/GridRenderer";
|
||||
import { errorNotice } from "../../../utils/notice";
|
||||
import { ConfigObject, loadConfig, saveConfig } from "src/utils/csvConfig";
|
||||
import { ColumnDefinition, ConfigObject, DEFAULT_CONFIG, loadConfig, saveConfig } from "src/utils/csvConfig";
|
||||
import { ColumnType } from "../../../utils/types";
|
||||
import { Settings } from "../Settings";
|
||||
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";
|
||||
|
||||
const delay = (n: number) => new Promise((resolve) => setTimeout(resolve, n));
|
||||
|
||||
|
|
@ -158,14 +160,25 @@ export class CSVView extends TextFileView {
|
|||
return "auto";
|
||||
}
|
||||
|
||||
getColumnConfig(columnName: string) {
|
||||
if (this.config.columnDefinitions[columnName]) {
|
||||
return this.config.columnDefinitions[columnName]
|
||||
}
|
||||
return { ...DEFAULT_CONFIG }
|
||||
}
|
||||
|
||||
async changeColumnType(columnName: string, type: ColumnType) {
|
||||
return await this.setConfig(columnName, { type })
|
||||
}
|
||||
|
||||
async setConfig(columnName: string, config: Partial<ColumnDefinition>) {
|
||||
const prev = this.config.columnDefinitions[columnName] ?? {};
|
||||
this.config.columnDefinitions[columnName] = {
|
||||
this.config.columnDefinitions[columnName] = {
|
||||
...prev,
|
||||
type: type,
|
||||
...config,
|
||||
};
|
||||
await this.saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
async loadConfig() {
|
||||
while (!this.file) {
|
||||
|
|
@ -179,25 +192,34 @@ export class CSVView extends TextFileView {
|
|||
}
|
||||
|
||||
private getColumnConfigurations(columns: string[]) {
|
||||
return columns.map((f) => {
|
||||
return columns
|
||||
.filter(f => this.showHidden || !this.getColumnConfig(f).isHidden)
|
||||
.map((f) => {
|
||||
if (!this.config) {
|
||||
return {
|
||||
field: f,
|
||||
};
|
||||
}
|
||||
const def = this.config.columnDefinitions[f]?.type;
|
||||
if (!def || def === "auto") {
|
||||
return { field: f };
|
||||
const config = this.getColumnConfig(f)
|
||||
const type = config.type
|
||||
const cellStyle = { opacity: config.isHidden ? 0.5 : 1 }
|
||||
const result = {
|
||||
field: f,
|
||||
cellStyle,
|
||||
headerClass: config.isHidden ? 'sqlseal-hidden-column' : ''
|
||||
}
|
||||
if (!type || type === "auto") {
|
||||
return result;
|
||||
}
|
||||
if (def === "date") {
|
||||
if (type === "date") {
|
||||
return {
|
||||
field: f,
|
||||
...result,
|
||||
cellDataType: "dateString",
|
||||
};
|
||||
}
|
||||
return {
|
||||
field: f,
|
||||
cellDataType: def,
|
||||
...result,
|
||||
cellDataType: type,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -283,6 +305,8 @@ export class CSVView extends TextFileView {
|
|||
|
||||
isLoading: boolean = false;
|
||||
|
||||
showHidden: boolean = false;
|
||||
|
||||
private async renderCSV() {
|
||||
if (this.isLoading) {
|
||||
if (this.api) {
|
||||
|
|
@ -323,6 +347,19 @@ export class CSVView extends TextFileView {
|
|||
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()
|
||||
})
|
||||
|
||||
const gridEl = csvEditorDiv.createDiv({ cls: "sql-seal-csv-viewer" });
|
||||
|
||||
generateSqlCode.addEventListener("click", (e) => {
|
||||
|
|
@ -346,69 +383,9 @@ export class CSVView extends TextFileView {
|
|||
headerComponentParams: {
|
||||
enableMenu: this.settings.get("enableEditing"),
|
||||
showColumnMenu: function (e: any) {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("Rename Column");
|
||||
item.onClick(() => {
|
||||
const modal = new RenameColumnModal(csvView.app, (res) => {
|
||||
csvView.renameColumn(
|
||||
this.column.userProvidedColDef.field,
|
||||
res,
|
||||
);
|
||||
});
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("Delete Column");
|
||||
item.onClick(() => {
|
||||
const colName = this.column.userProvidedColDef.field;
|
||||
const modal = new DeleteConfirmationModal(
|
||||
csvView.app,
|
||||
`column ${colName}`,
|
||||
() => {
|
||||
csvView.deleteColumn(colName);
|
||||
},
|
||||
);
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
|
||||
// FIXME: rework it to submenus.
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => {
|
||||
// item.setDisabled(true)
|
||||
item.setTitle("Data Type");
|
||||
// item.setIsLabel(true)
|
||||
const ipfSubmenu = (item as any).setSubmenu();
|
||||
const types = [
|
||||
"auto",
|
||||
"text",
|
||||
"number",
|
||||
"boolean",
|
||||
"date",
|
||||
] as ColumnType[];
|
||||
|
||||
const colName = this.column.userProvidedColDef.field;
|
||||
|
||||
const current = csvView.getColumnType(colName);
|
||||
types.forEach((type) => {
|
||||
ipfSubmenu.addItem((subItem: MenuItem) => {
|
||||
const checkbox = type === current ? "✓ " : "";
|
||||
subItem.setTitle(checkbox + type);
|
||||
subItem.onClick(() => {
|
||||
csvView.changeColumnType(colName, type);
|
||||
csvView.refreshTypes();
|
||||
csvView.loadDataIntoGrid();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const pos = e.getBoundingClientRect();
|
||||
menu.showAtPosition({ x: pos.x, y: pos.y + 20 });
|
||||
const menu = new CSVColumnContextMenu(csvView, this.column as AgColumn)
|
||||
const pos = e.getBoundingClientRect();
|
||||
menu.show({ x: pos.x, y: pos.y + 20 })
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -59,14 +59,26 @@ export class CsvFileSyncStrategy extends ISyncStrategy {
|
|||
header: true,
|
||||
dynamicTyping: false,
|
||||
skipEmptyLines: true,
|
||||
transformHeader: sanitise
|
||||
transformHeader: sanitise,
|
||||
})
|
||||
|
||||
const config = await loadConfig(file, this.app.vault)
|
||||
|
||||
return { data: parsed.data, columns: parsed.meta.fields?.map(f => ({
|
||||
name: f,
|
||||
type: config.columnDefinitions[f]?.type ?? 'auto' as const
|
||||
})) ?? [] }
|
||||
const columns = parsed.meta.fields?.
|
||||
filter(f => !config.columnDefinitions[f]?.isHidden)
|
||||
.map(f => ({
|
||||
name: f,
|
||||
type: config.columnDefinitions[f]?.type ?? 'auto' as const
|
||||
})) ?? []
|
||||
|
||||
const resultedData = parsed.data.map(d => {
|
||||
const res: Record<string, any> = {}
|
||||
for (const c of columns) {
|
||||
res[c.name] = d[c.name]
|
||||
}
|
||||
return res
|
||||
})
|
||||
|
||||
return { data: resultedData, columns }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
/* AG GRID CUSTOMS */
|
||||
.ag-checkbox-input {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.sqlseal-hidden-column {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
@ -5,10 +5,16 @@ import { parse as jsonParse, stringify as jsonStringify } from 'json5'
|
|||
// export type ColumnType = 'auto' | 'number' | 'text'
|
||||
export type ColumnType = string
|
||||
|
||||
interface ColumnDefinition {
|
||||
export interface ColumnDefinition {
|
||||
type: ColumnType
|
||||
isHidden: boolean
|
||||
}
|
||||
|
||||
export const DEFAULT_CONFIG = {
|
||||
type: 'auto',
|
||||
isHidden: false
|
||||
} satisfies ColumnDefinition
|
||||
|
||||
export interface ConfigObject {
|
||||
columnDefinitions: {[key: string]: ColumnDefinition }
|
||||
}
|
||||
|
|
@ -27,7 +33,13 @@ export const loadConfig = async (file: TFile, vault: Vault): Promise<ConfigObjec
|
|||
const loadedConfig = await vault.read(configFile)
|
||||
// decode
|
||||
const decoded = jsonParse(loadedConfig)
|
||||
return decoded
|
||||
|
||||
// MAKE SURE ALL KEYS ARE LOWERCASE
|
||||
const columnDefinitions = Object.fromEntries(Object.entries(decoded?.columnDefinitions ?? {}).map(([key, value]) => ([key.toLowerCase(), value])))
|
||||
return {
|
||||
...decoded,
|
||||
columnDefinitions
|
||||
}
|
||||
}
|
||||
|
||||
export const saveConfig = async (file: TFile, content: Object, vault: Vault) => {
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
.sqlseal-table-container{overflow-y:scroll}.sqlseal-table-container table{width:100%;border-collapse:collapse}.sqlseal-grid-wrapper{height:auto;position:relative}.block-language-sqlseal{overflow-y:auto}.sqlseal-error{padding:1em;background:#b80f0f;color:#fff}.sqlseal-notice{padding:1em;background:#1f3f98;color:#fff}.sqlseal-grid-error-message{box-sizing:border-box;width:80%;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;background:#b80f0f;font-size:.8em;color:#fff;padding:.5em 2em}.sqlseal-grid-error-message-overlay{content:"";position:absolute;inset:0;background:#fffc;z-index:5}.sqlseal-grid-error-message-overlay.hidden{display:none}.edit-block-button{z-index:1000}.sqlseal-markdown-table{font-family:monospace;white-space:pre-wrap}.sqlseal-markdown-table.no-wrap{display:inline-block;overflow-x:scroll;width:fit-content}.sqlseal-parse-error{background:#b80f0f;color:#fff;padding:.2em;border-radius:2px}.sqlseal-notice-error{color:#f26464}.sqlseal-notice-error:before{content:"";width:16px;height:16px;display:inline-block;margin-right:8px;transform:translateY(3px);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ff0000' d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z'/%3E%3C/svg%3E")}.sql-seal-csv-editor{display:flex;flex-direction:column;height:100%;gap:var(--size-4-4)}.sql-seal-csv-viewer{flex:1;display:flex;flex-direction:column}.sqlseal-grid-wrapper{height:100%;flex:1;display:flex;flex-direction:column}.ag-theme-quartz{flex:1}.sql-seal-csv-viewer-buttons{display:flex;gap:var(--size-4-4)}.sql-seal-modal-code{width:100%;font-family:monospace;padding:8px}.sqlseal-inline-result{display:inline-block;padding:0 4px;margin:0 2px;background-color:var(--background-secondary);border-radius:4px;font-size:.9em;cursor:pointer}.sqlseal-inline-result .error{color:var(--text-error);font-style:italic}.sqlseal-list-element-single .sqlseal-column-name{display:none}.sqlseal-list-element-single .sqlseal-column-name:after{content:": "}.sqlseal-list.show-column-names .sqlseal-list-element-single .sqlseal-column-name{display:inline}.sqlseal-element-inline-list{display:inline-block}.cm-sqlseal-block-query{--color: #297f30}.cm-sqlseal-block-view{--color: #c834dd}.cm-sqlseal-block-flag{--color: #d08306;font-weight:700;color:var(--color)}.cm-sqlseal-block-table{--color: #4985af}.cm-sqlseal-identifier{color:#2d7b33}.cm-sqlseal-function,.cm-sqlseal-parameter{color:#2c0e9b}.cm-sqlseal-comment{color:#7a7a7a;font-style:italic}.cm-sqlseal-keyword{color:var(--color);font-weight:700}.cm-sqlseal-literal{color:#03f}.cm-sqlseal-error{color:#eb2f2f;font-style:italic}.cm-sqlseal-template-keyword{color:#e3ab53}.theme-dark .cm-sqlseal-template-keyword{color:#dd8e10}.theme-dark .cm-sqlseal-block-error{color:red}.theme-dark .cm-sqlseal-block-query{--color: #5eff6a}.theme-dark .cm-sqlseal-comment{color:#898787}.theme-dark .cm-sqlseal-parameter{color:#95ed22}.theme-dark .cm-sqlseal-literal{color:#ff0}.theme-dark .cm-sqlseal-block-view{--color: #e945ff}.theme-dark .cm-sqlseal-block-flag{--color: #e3ab53}.theme-dark .cm-sqlseal-block-table{--color: #56b5fa}.theme-dark .cm-sqlseal-function{color:#c4b8ef}.theme-dark .cm-sqlseal-identifier{color:#6ff77a}:is(.cm-sqlseal-block-query,.cm-sqlseal-block-view,.cm-sqlseal-block-flag,.cm-sqlseal-block-table):before{background-color:var(--color);width:5px;top:-3px;bottom:-3px;left:0;content:"";display:block;position:absolute}.cm-indent+:is(.cm-sqlseal-block-query,.cm-sqlseal-block-view,.cm-sqlseal-block-flag,.cm-sqlseal-block-table):before{display:none}.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>:has(>.block-language-sqlseal table.dataview){width:var(--container-dataview-table-width);max-width:var(--container-table-max-width)}.ag-checkbox-input{opacity:1!important}.sqlseal-settings-warn{background:#ffa600;padding:16px;border:2px solid rgb(134,87,0);border-radius:5px;margin:.5em 2em}
|
||||
/*# sourceMappingURL=styles.css.map */
|
||||
Loading…
Reference in a new issue