diff --git a/main.ts b/main.ts index b17d8b3..e1ed27b 100644 --- a/main.ts +++ b/main.ts @@ -1,12 +1,12 @@ -import { readCSV_, saveCSV_} from 'src/csvfilemanager'; -import { createCsvInputModal_, createCsvTableView_ } from './src/csvdisplay'; -import { CSVTable } from './src/types' - import { - App, Modal, Plugin, PluginSettingTab, Setting, - TFile, + App, Plugin, PluginSettingTab, Setting, } from 'obsidian'; -import { createInflate } from 'zlib'; + +import { createCsvInputModal_, createCsvTableView_ } from './src/csvPlugin'; +import { readCSV_, saveCSV_} from 'src/csvFilemanager'; +import { CSVTable, Header } from './src/types' +import CsvExplorerModal, { getCsvFileStructure } from 'src/csvExplorer'; +import CsvCreateModal, { createCsvFile_ } from 'src/csvCreator'; interface CsvPluginSettings { mySetting: string; @@ -28,13 +28,15 @@ export default class CsvPlugin extends Plugin { } //// csvdisplay.ts - openCsvInputModal = async (app: App, headers: string[], fileName: string) => { + openCsvInputModal = async (app: App, headers: Header[], fileName: string) => { createCsvInputModal_(app, headers, fileName); } - createCsvTableView = (csvTable: CSVTable): string => { + createCsvTableView = (csvTable: CSVTable): HTMLElement => { return createCsvTableView_(csvTable); } - + createCsvFile = (filename: string, filePath: string, columnData: { name:string, type: string }[]) => { + createCsvFile_(this.app, filename, filePath, columnData); + } async onload() { await this.loadSettings(); @@ -43,6 +45,24 @@ export default class CsvPlugin extends Plugin { // This adds a settings tab so the user can configure various aspects of the plugin this.addSettingTab(new CsvSettingTab(this.app, this)); + + // commands + this.addCommand({ + id: "create-csv-table", + name: "Create CSV Table", + callback: () => { + new CsvCreateModal(this.app).open(); + }, + }); + + this.addCommand({ + id: "open-csv-explorer", + name: "Open CSV Explorer", + callback: async () => { + const csvStructure = await getCsvFileStructure(this.app); + new CsvExplorerModal(this.app, csvStructure).open(); + } + }) } onunload() {} diff --git a/manifest.json b/manifest.json index bd10535..91491ee 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,11 @@ { "id": "csv-allinone", "name": "CSV All-in-One", - "version": "0.1.1", + "version": "0.1.0", "minAppVersion": "1.7.7", "description": "all about csv.", "author": "hihangeol", "authorUrl": "https://github.com/Hangeol-Chang/obsidian-csv-allinone", - "fundingUrl": "https://github.com/Hangeol-Chang/obsidian-csv-allinone", + "fundingUrl": "https://buymeacoffee.com/hihangeol", "isDesktopOnly": false -} +} \ No newline at end of file diff --git a/src/csvCreateor.ts b/src/csvCreateor.ts new file mode 100644 index 0000000..e9ad3f8 --- /dev/null +++ b/src/csvCreateor.ts @@ -0,0 +1,168 @@ +import { App, Modal, Notice, Plugin } from 'obsidian'; +import './styles/csvCreator.css'; +import { columnTypes } from './types'; + +// dataviewjs 등으로 자동적으로 파일을 생성하게 할 때 사용할 수 있는 api +export const createCsvFile_ = (app: App, filename: string, filePath: string, columnData: { name:string, type: string }[]) : boolean => { + // .csv 내용 생성 (헤더만 추가) + const csvContent = columnData.map((col) => col.name).join(',') + '\n'; + + // .csv.meta 내용 생성 + const metaContent = JSON.stringify( + columnData.reduce((acc, col) => { + acc[col.name] = col.type; + return acc; + }, {} as Record), + null, + 2 + ); + + // 파일 저장 + const vault = app.vault; + const fullPathCsv = `${filePath}/${filename}.csv`; + const fullPathMeta = `${filePath}/${filename}.csv.meta`; + + // .csv 파일 저장 + vault.adapter.write(fullPathCsv, csvContent).then(() => { + new Notice(`${fullPathCsv} file has been created.`); + }).catch((err) => { + new Notice(`Error occurred while creating .csv file:\n${err}`); + return false; + }); + + // .csv.meta 파일 저장 + vault.adapter.write(fullPathMeta, metaContent).then(() => { + new Notice(`${fullPathMeta} file has been created.`); + }).catch((err) => { + new Notice(`Error occurred while creating .csv.meta file:\n${err}`); + return false; + }); + return true; +} + +export default class CsvCreateModal extends Modal { + + columnsWrapper: HTMLElement; + + constructor(app: App) { + super(app); + } + + addColumn() { + let inputWarpper = this.columnsWrapper.createEl('tr'); + let columnTd = inputWarpper.createEl('td'); + let columnInput = columnTd.createEl('input'); + columnInput.classList.add('colname-input'); + columnInput.placeholder = 'name'; + + let typeTd = inputWarpper.createEl('td'); + let typeSelect = typeTd.createEl('select'); + typeSelect.classList.add('coltype-select'); + for (let type of columnTypes) { + let option = typeSelect.createEl('option'); + option.value = type; + option.text = type; + }; + } + + createCsvFile() { + // .csv 파일 생성 + const { contentEl } = this; + + // 파일 이름과 경로 가져오기 + const filenameInput = contentEl.querySelector('.filename-input') as HTMLInputElement; + const filePathInput = contentEl.querySelector('.filepath-input') as HTMLInputElement; + + let filename = filenameInput.value.trim(); + let filePath = filePathInput.value.trim() || '/'; + + // validation + if (!filename) { + new Notice('Please enter a file name.'); + return false; + } + if(filename.endsWith('.csv')) { + filename = filename.slice(0, -4); + } + if(filePath.length > 1 && filePath.endsWith('/')) { + filePath = filePath.slice(0, -1); + } + + // 컬럼 정보 수집 + const columnData: { name: string; type: string }[] = []; + this.columnsWrapper.querySelectorAll('tr').forEach((columnEl) => { + const inputEl = columnEl.querySelector('input') as HTMLInputElement; + const selectEl = columnEl.querySelector('select') as HTMLSelectElement; + + const columnName = inputEl?.value.trim(); + const columnType = selectEl?.value; + + if (columnName && columnType) { + columnData.push({ name: columnName, type: columnType }); + } + }); + + if (columnData.length === 0) { + new Notice('add at least one column'); + return false; + } + + return createCsvFile_(this.app, filename, filePath, columnData); + } + + // common api + onOpen() { + let {contentEl} = this; + contentEl.createEl('h2', {text: 'Create CSV Table'}); + contentEl.createEl('hr'); + + // file setting + let filenameContainer = contentEl.createEl('div'); + filenameContainer.classList.add('filename-container'); + + let filenameEl = filenameContainer.createEl('input'); + filenameEl.placeholder = 'Title'; + filenameEl.classList.add('filename-input'); + + let filePathEl = filenameContainer.createEl('input'); + filePathEl.placeholder = 'file path(default: root)'; + filePathEl.classList.add('filepath-input'); + + // add column button + let addColumnButton = contentEl.createEl('button', {text: 'Add Column'}); + addColumnButton.addEventListener('click', () => { this.addColumn(); }); + + // column making table + let columnsTable = contentEl.createEl('table'); + columnsTable.classList.add('columns-table'); + let tHead = columnsTable.createEl('thead'); + let tHeadRow = tHead.createEl('tr'); + tHeadRow.classList.add('columns-table-header'); + + tHeadRow.createEl('th', {text: 'Column Name'}); + tHeadRow.createEl('th', {text: 'Type'}); + + columnsTable.createEl('hr'); + this.columnsWrapper = columnsTable.createEl('tbody'); + this.columnsWrapper.classList.add('columns-wrapper'); + + // submit button + contentEl.createEl('hr'); + let buttonEl = contentEl.createEl('button', {text: 'Create'}); + buttonEl.addEventListener('click', () => { + event?.preventDefault(); + const result = this.createCsvFile(); + if(result) { + this.close(); + } + else { + new Notice('Error occurred while creating .csv file'); + } + }); + } + + onClose() { + let {contentEl} = this; + contentEl.empty(); + } +} \ No newline at end of file diff --git a/src/csvExplorer.ts b/src/csvExplorer.ts new file mode 100644 index 0000000..03cf7fa --- /dev/null +++ b/src/csvExplorer.ts @@ -0,0 +1,52 @@ +import { App, Modal, TFile } from 'obsidian'; + +/* + obsidian 탐색기에서 csv 파일이 표시가 안되기 때문에,\ + 파일 탐색하고 지우는 용도로 사용. +*/ +export const getCsvFileStructure = async (app: App): Promise> => { + const csvStructure: Record = {}; + const files = app.vault.getFiles(); + + // Vault 내의 모든 파일 순회 + files.forEach((file: TFile) => { + if (file.extension === "csv") { + if(file.parent) { + const folderPath = file.parent.path; + if (!csvStructure[folderPath]) + csvStructure[folderPath] = []; + + csvStructure[folderPath].push(file.name); + } + } + }); + return csvStructure; +} + +export default class CsvExplorerModal extends Modal { + csvStructure: Record = {}; + + constructor(app: App, csvStructure: Record) { + super(app); + this.csvStructure = csvStructure; + } + + onOpen() { + const { contentEl } = this; + contentEl.empty(); + + contentEl.createEl("h2", { text: "CSV Explorer" }); + + Object.keys(this.csvStructure).forEach((folder) => { + contentEl.createEl("h3", { text: folder }); + this.csvStructure[folder].forEach((file) => { + contentEl.createEl("p", { text: file }); + }); + }); + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + } +} \ No newline at end of file diff --git a/src/csvFilemanager.ts b/src/csvFilemanager.ts new file mode 100644 index 0000000..8ac2f59 --- /dev/null +++ b/src/csvFilemanager.ts @@ -0,0 +1,94 @@ +import { CSVTable, Header } from './types' +import { App, TFile } from 'obsidian'; + +// meta file을 수정하는 로직이 포함되어야 함. + +// public api +export const readCSV_ = async (app: App, fileName: string): Promise => { + if(fileName.endsWith(".csv")) { // 확장자 검사. + // read .csv file, + const csvContent = await loadFile(app, fileName); // 파일 로드. + + const lines = csvContent.split("\n").map(line => line.trim()); + const headersString = lines.shift()?.split(",") ?? []; + const rows = lines.map(line => line.split(",").map(cell => cell.trim())); + + // read .csv.meta file, + // meta file 존재 여부 확인해서 없으면 생성. + if(await app.vault.adapter.exists(`${fileName}.meta`) == false) { + const headers: { name: string, type: string }[] = + headersString.map((header) => ({ name: header, type: "string" })); + + const metaContent = JSON.stringify( + headers.reduce((acc, col) => { + acc[col.name] = col.type; + return acc; + }, {} as Record), + null, + 2 + ); + // const metaContent = JSON.stringify(headers, null, 2); + await saveFile(app, `${fileName}.meta`, metaContent); + } + + + // meta file 로드. + const metaData = await loadFile(app, `${fileName}.meta`); // 메타 파일 로드. + const headers: Header[] = Object.entries(JSON.parse(metaData)); + + return await new CSVTable(headers, rows) // CSV 파싱. + } else { + console.error(`file extension is not csv : ${fileName} (read)`); + } + return null; +} +export const saveCSV_ = async (app: App, fileName: string, table: CSVTable): Promise => { + if(fileName.endsWith(".csv")) { + // contents, meta로 분리 필요. + const content = table.toCSV(); + await saveFile(app, fileName, content); + // const meta = table.getMeta(); + // await saveMetaFile(app, `${fileName}.meta`, meta); + } else { + console.error(`file extension is not csv : ${fileName} (save)`); + } + return; +} + +// save, load +const saveFile = async (app: App, fileName: string, content: string): Promise => { + const vault = app.vault; + + const existingFile = vault.getAbstractFileByPath(fileName); + if (existingFile instanceof TFile) { + // 파일이 존재하면 덮어쓰기 + // console.log(`file exists, modify : ${fileName}`); + await vault.modify(existingFile, content); + } else { + // 파일이 존재하지 않으면 새로 생성 + // console.log(`file not exists, create : ${fileName}`); + await vault.create(fileName, content); + } +} +const saveMetaFile = async (app: App, fileName: string, content: Header[]): Promise => { + +} + +const loadFile = async (app: App, fileName: string): Promise => { + const vault = app.vault; + const file = vault.getAbstractFileByPath(fileName); + if (file instanceof TFile) { + return await vault.read(file); + } else { + throw new Error('file not found'); + } +} + +// api 재설계 필요. -> 애초에 사용할 일 있는지 모르겠음. +// const parseCSV = (content: string): CSVTable => { + // const lines = content.split("\n").map(line => line.trim()); + // const headers = lines.shift()?.split(",") ?? []; + // const rows = lines.map(line => line.split(",").map(cell => cell.trim())); + + // return new CSVTable(headers, rows); +// } \ No newline at end of file diff --git a/src/csvPlugin.ts b/src/csvPlugin.ts new file mode 100644 index 0000000..49a9c55 --- /dev/null +++ b/src/csvPlugin.ts @@ -0,0 +1,169 @@ +import { App, Modal } from "obsidian"; +import { CSVRow, CSVTable, Header } from "./types"; +import { readCSV_, saveCSV_ } from "./csvFilemanager"; +import './styles/csvPlugin.css'; + +export const createCsvInputModal_ = async (app: App, headers: Header[], fileName: string) => { + const form = generateForm(fileName, headers); + + const modal = new Modal(app); + modal.contentEl.empty(); + modal.contentEl.appendChild(form); + modal.open(); + + // 파일을 새로 읽어서, 추가된 라인을 포함 저장. + const formElement = modal.contentEl.querySelector('#csv-input-form') as HTMLFormElement; + formElement.addEventListener('submit', async (event) => { + event.preventDefault(); // 기본 제출 동작 방지 + // 입력 값 처리 가져오기. + const formData = new FormData(formElement); + + const newRow: CSVRow = []; + for(const header of headers) { + newRow.push(formData.get(header[0]) as string); + } + + // 파일 읽기 + const table = await readCSV_(app, fileName); + if(table === null) { + console.error("file not found"); + return; + } + // 파일 저장 + table?.append(newRow); + // console.log(table); + saveCSV_(app, fileName, table); + modal.close(); + }); +} + +const generateForm = (fileName: string, headers: Header[]): HTMLFormElement => { + const form = document.createElement("form"); + form.id = "csv-input-form"; + + const fileNameArray = fileName.split('/'); + const fileNameShort = fileNameArray[fileNameArray.length - 1]; + + const titleTinkle = document.createElement("h6"); + titleTinkle.textContent = "Add new row to"; + const title = document.createElement("h2"); + title.textContent = fileNameShort; + + form.appendChild(titleTinkle); + form.appendChild(title); + + // 수평선 삽입. + form.appendChild(document.createElement("hr")); + + // 폼 요소 생성 + // 입력 필드 생성 및 추가 + headers.forEach(header => { + const field = createHeaderInputField(header); + form.appendChild(field); + }); + + // 버튼 그룹 생성 및 추가 + const buttonGroup = document.createElement("div"); + buttonGroup.className = "button-group"; + + const submitButton = document.createElement("button"); + submitButton.className = "submit-button"; + submitButton.type = "submit"; + submitButton.textContent = "Submit"; + + buttonGroup.appendChild(submitButton); + form.appendChild(buttonGroup); + + return form; +} + +const createHeaderInputField = (key: [string, string]): HTMLDivElement => { + // 입력 필드 래퍼 생성 + const formGroup = document.createElement("div"); + formGroup.className = "form-group"; + + // 라벨 생성 + const label = document.createElement("label"); + label.className = "input-key"; + label.htmlFor = key[0]; + label.textContent = key[0]; + + // 입력 필드 생성 + const input = document.createElement("input"); + input.className = "input-value"; + input.type = "text"; + input.id = key[0]; + input.name = key[0]; + input.placeholder = key[1]; + + // 요소 추가 + formGroup.appendChild(label); + formGroup.appendChild(input); + + return formGroup; +} + +const createInputField = (key: string): HTMLDivElement => { + // 입력 필드 래퍼 생성 + const formGroup = document.createElement("div"); + formGroup.className = "form-group"; + + // 라벨 생성 + const label = document.createElement("label"); + label.className = "input-key"; + label.htmlFor = key; + label.textContent = key; + + // 입력 필드 생성 + const input = document.createElement("input"); + input.className = "input-value"; + input.type = "text"; + input.id = key; + input.name = key; + + // 요소 추가 + formGroup.appendChild(label); + formGroup.appendChild(input); + + return formGroup; +} + +export const createCsvTableView_ = (csvTable: CSVTable): HTMLElement => { + const headers = csvTable.getHeaders(); + const rows = csvTable.getRows(); + + // 테이블 래퍼 생성 + const tableWrapper = document.createElement("div"); + tableWrapper.className = "table-wrapper"; + + // 테이블 생성 + const table = document.createElement("table"); + + // 테이블 헤더 생성 + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + headers.forEach(header => { + const th = document.createElement("th"); + th.textContent = header[0]; + headerRow.appendChild(th); + }); + thead.appendChild(headerRow); + table.appendChild(thead); + + // 테이블 바디 생성 + const tbody = document.createElement("tbody"); + rows.forEach(row => { + const tableRow = document.createElement("tr"); + row.forEach(cell => { + const td = document.createElement("td"); + td.textContent = cell?.toString() ?? ""; + tableRow.appendChild(td); + }); + tbody.appendChild(tableRow); + }); + table.appendChild(tbody); + + tableWrapper.appendChild(table); + + return tableWrapper; +} \ No newline at end of file diff --git a/src/csvdisplay.ts b/src/csvdisplay.ts deleted file mode 100644 index df9688f..0000000 --- a/src/csvdisplay.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { App, Modal } from "obsidian"; -import { CSVRow, CSVTable } from "./types"; -import { readCSV_, saveCSV_ } from "./csvfilemanager"; -import './styles/modal.css'; - -export const createCsvInputModal_ = async (app: App, headers: string[], fileName: string) => { - const form = generateForm(headers); - - const modal = new Modal(app); - modal.contentEl.innerHTML = form; - modal.open(); - - // 파일을 새로 읽어서, 추가된 라인을 포함 저장. - const formElement = modal.contentEl.querySelector('#csv-input-form') as HTMLFormElement; - formElement.addEventListener('submit', async (event) => { - event.preventDefault(); // 기본 제출 동작 방지 - // 입력 값 처리 가져오기. - const formData = new FormData(formElement); - - const newRow: CSVRow = []; - for(const header of headers) { - newRow.push(formData.get(header) as string); - } - - // 파일 읽기 - const table = await readCSV_(app, fileName); - if(table === null) { - console.error("file not found"); - return; - } - // 파일 저장 - table?.append(newRow); - // console.log(table); - saveCSV_(app, fileName, table); - modal.close(); - }); -} - -const generateForm = (headers: string[]): string => { - // 입력 필드를 HTML로 생성 - const fields = headers.map(header => createInputField(header)); - // 폼 래퍼에 입력 필드를 삽입 - return ` -
- ${fields.join("\n")} -
-
- -
-
- `; -} -const createInputField = (key: string): string => { - return ` -
- - -
- `; -} - -export const createCsvTableView_ = (csvTable: CSVTable): string => { - const headers = csvTable.getHeaders(); - const rows = csvTable.getRows(); - - const table = rows.map(row => { - return ` - - ${row.map(cell => `${cell}`).join("")} - - `; - }).join(""); - - return ` -
- - - - ${headers.map(header => ``).join("")} - - - - ${table} - -
${header}
-
- `; -} \ No newline at end of file diff --git a/src/csvfilemanager.ts b/src/csvfilemanager.ts deleted file mode 100644 index 62d74fc..0000000 --- a/src/csvfilemanager.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { CSVTable } from './types' -import { App, TFile } from 'obsidian'; - -export const readCSV_ = async (app: App, fileName: string): Promise => { - if(fileName.endsWith(".csv")) { // 확장자 검사. - const content = await loadFile(app, fileName); // 파일 로드. - return await new CSVTable(content) // CSV 파싱. - } else { - console.error(`file extension is not csv : ${fileName} (read)`); - } - return null; -} -export const saveCSV_ = async (app: App, fileName: string, table: CSVTable): Promise => { - if(fileName.endsWith(".csv")) { - const content = table.toCSV(); - await saveFile(app, fileName, content); - } else { - console.error(`file extension is not csv : ${fileName} (save)`); - } - return; -} - -// save, load -const saveFile = async (app: App, fileName: string, content: string): Promise => { - const vault = app.vault; - - const existingFile = vault.getAbstractFileByPath(fileName); - if (existingFile instanceof TFile) { - // 파일이 존재하면 덮어쓰기 - // console.log(`file exists, modify : ${fileName}`); - await vault.modify(existingFile, content); - } else { - // 파일이 존재하지 않으면 새로 생성 - // console.log(`file not exists, create : ${fileName}`); - await vault.create(fileName, content); - } -} -const loadFile = async (app: App, fileName: string): Promise => { - const vault = app.vault; - const file = vault.getAbstractFileByPath(fileName); - if (file instanceof TFile) { - return await vault.read(file); - } else { - throw new Error('file not found'); - } -} - -const parseCSV = (content: string): CSVTable => { - const lines = content.split("\n").map(line => line.trim()); - const headers = lines.shift()?.split(",") ?? []; - const rows = lines.map(line => line.split(",").map(cell => cell.trim())); - - return new CSVTable(headers, rows); -} \ No newline at end of file diff --git a/src/styles/csvCreator.css b/src/styles/csvCreator.css new file mode 100644 index 0000000..938b390 --- /dev/null +++ b/src/styles/csvCreator.css @@ -0,0 +1,75 @@ +.filename-container { + display: flex; + flex-direction: row; + gap: 1rem; + align-items: center; +} + +.filename-input { + width: 60%; + flex-grow : 2; + padding: 0.5rem; + margin: 0.5rem 0; + border: 1px solid #ccc; + border-radius: 0.25rem; + font-size: 1rem; + font-family: var(--font-monospace); + color: var(--text-normal); + background-color: var(--background-secondary); +} +.filepath-input { + width: 30%; + flex-grow: 1; + padding: 0.5rem; + margin: 0.5rem 0; + border: 1px solid #ccc; + border-radius: 0.25rem; + font-size: 1rem; + font-family: var(--font-monospace); + color: var(--text-normal); + background-color: var(--background-secondary); +} + +hr { + margin: 5px 0; + border: 0; + width: 100%; + border-top: 1px solid #ccc; +} + +.columns-table { + width: 100%; +} + +.columns-wrapper { + width: 100%; +} +.columns-wrapper > :nth-child(2n) { + background-color: var(--background-secondary); +} + +.columns-table-header { + gap: 1rem; + align-items: center; +} + +.colname-input { + padding: 2px; + width: 100%; height: 30px; + border: 1px solid #ccc; + border-radius: 0.25rem; + font-size: 1rem; + font-family: var(--font-monospace); + color: var(--text-normal); +} + +.coltype-select { + padding: 2px; + width: 100%; height: 30px; + border: 1px solid #ccc; + border-radius: 0.25rem; + font-size: 1rem; + font-family: var(--font-monospace); + color: var(--text-normal); +} + diff --git a/src/styles/modal.css b/src/styles/csvPlugin.css similarity index 90% rename from src/styles/modal.css rename to src/styles/csvPlugin.css index e814c7d..2740a7b 100644 --- a/src/styles/modal.css +++ b/src/styles/csvPlugin.css @@ -18,6 +18,10 @@ padding: 1rem; } +.input-type { + background-color: grey; +} + .input-key { text-align: left; margin-right: 0.5rem; diff --git a/src/types.ts b/src/types.ts index 94a7cd0..a622bc9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,24 +1,29 @@ export type CSVCell = string | number | null; export type CSVRow = CSVCell[]; +export type Header = [string, string]; // [columnName, columnType] export class CSVTable { - private headers: string[]; + private headers: Header[]; private rows: CSVRow[]; // string으로 넣거나, 분리된 headers/rows로 생성자 호출 가능. - constructor(contentOrHeaders: string | string[], rows: CSVRow[] = []) { - if (typeof contentOrHeaders === 'string') { - // content가 문자열일 때 - const lines = contentOrHeaders.split("\n").map(line => line.trim()); - this.headers = lines.shift()?.split(",") ?? []; - this.rows = lines.map(line => line.split(",").map(cell => cell.trim())); - } else { - // headers가 배열일 때 - this.headers = contentOrHeaders; - this.rows = rows; - } + constructor(headers: Header[], rows: CSVRow[] = []) { + this.headers = headers; + this.rows = rows; } + // constructor(contentOrHeaders: string | string[], rows: CSVRow[] = []) { + // if (typeof contentOrHeaders === 'string') { + // // content가 문자열일 때 + // const lines = contentOrHeaders.split("\n").map(line => line.trim()); + // this.headers = lines.shift()?.split(",") ?? []; + // this.rows = lines.map(line => line.split(",").map(cell => cell.trim())); + // } else { + // // headers가 배열일 때 + // this.headers = contentOrHeaders; + // this.rows = rows; + // } + // } - getHeaders(): string[] { + getHeaders(): Header[] { return this.headers; } getRows(): CSVRow[] { @@ -35,8 +40,9 @@ export class CSVTable { this.rows.push(row); } else { + // key-value로 들어올 때 const newRow: CSVRow = []; - for(const header of this.headers) { newRow.push(row[header]); } + for(const header of this.headers) { newRow.push(row[header[0]]); } this.rows.push(newRow); } } @@ -48,7 +54,7 @@ export class CSVTable { this.rows.splice(rowIndex, 1); } updateCell(rowIndex: number, columnName: string, value: CSVCell): void { - const columnIndex = this.headers.indexOf(columnName); + const columnIndex = this.headers.findIndex(([colname]) => colname === columnName); if (columnIndex === -1) { throw new Error(`Column '${columnName}' does not exist.`); } @@ -67,14 +73,14 @@ export class CSVTable { } // 실제로 사용하게될 api일지는 모르겠습니다. - addColoumn(columnName: string, value: CSVCell = 0): void { - this.headers.push(columnName); + addColoumn(columnName: string, columnType: string, value: CSVCell = 0): void { + this.headers.push([columnName, columnType]); for (let i = 0; i < this.rows.length; i++) { this.rows[i].push(value); } } deleteColumn(columnName: string): void { - const columnIndex = this.headers.indexOf(columnName); + const columnIndex = this.headers.findIndex(([colname]) => colname === columnName); if (columnIndex === -1) { throw new Error(`Column '${columnName}' does not exist.`); } @@ -88,4 +94,10 @@ export interface CSVFile { name: string; path: string; content: CSVTable; -} \ No newline at end of file +} + +export const columnTypes: string[] = [ + 'string', + 'number', + 'boolean', +]; \ No newline at end of file