Functionally working, now.
This commit is contained in:
parent
229c0860da
commit
2ba1a590d2
|
@ -1,5 +1,5 @@
|
|||
import { Vault } from "@padloc/core/src/vault";
|
||||
import { VaultItem, FIELD_DEFS } from "@padloc/core/src/item";
|
||||
import { VaultItem, FIELD_DEFS, FieldType } from "@padloc/core/src/item";
|
||||
import { translate as $l } from "@padloc/locale/src/translate";
|
||||
import * as imp from "../lib/import";
|
||||
import { prompt, alert } from "../lib/dialog";
|
||||
|
@ -8,16 +8,15 @@ import { Select } from "./select";
|
|||
import { Dialog } from "./dialog";
|
||||
import "./button";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { html /*, css*/ } from "lit";
|
||||
import { saveFile } from "@padloc/core/src/platform";
|
||||
import { stringToBytes } from "@padloc/core/src/encoding";
|
||||
|
||||
// TODO: Force showing "Name" and "Tags" as well, as those are necessary for the import
|
||||
const fieldTypeOptions = Object.keys(FIELD_DEFS).map((fieldType) => ({
|
||||
name: FIELD_DEFS[fieldType].name as string,
|
||||
label: FIELD_DEFS[fieldType].name as string,
|
||||
value: fieldType,
|
||||
}));
|
||||
// TODO: Remove this
|
||||
console.log(fieldTypeOptions);
|
||||
|
||||
@customElement("pl-import-dialog")
|
||||
export class ImportDialog extends Dialog<File, void> {
|
||||
|
@ -36,6 +35,15 @@ export class ImportDialog extends Dialog<File, void> {
|
|||
@query("#vaultSelect")
|
||||
private _vaultSelect: Select<Vault>;
|
||||
|
||||
// static styles = [
|
||||
// ...Dialog.styles,
|
||||
// css`
|
||||
// :host {
|
||||
// --pl-dialog-max-width: 40em;
|
||||
// }
|
||||
// `,
|
||||
// ];
|
||||
|
||||
renderContent() {
|
||||
return html`
|
||||
<div class="padded vertical spacing layout">
|
||||
|
@ -50,20 +58,39 @@ export class ImportDialog extends Dialog<File, void> {
|
|||
></pl-select>
|
||||
|
||||
<div class="small padded" ?hidden=${this._formatSelect && this._formatSelect.value !== imp.CSV.value}>
|
||||
${$l("If you don't want to map columns to field types in the next step")}
|
||||
${$l("Choose the field type for each column. If you are having trouble,")}
|
||||
<a href="#" @click=${this._downloadCSVSampleFile}> ${$l("Download Sample File")} </a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="vertical evenly stretching spacing layout"
|
||||
class="vertical stretching spacing layout"
|
||||
?hidden=${this._formatSelect && this._formatSelect.value !== imp.CSV.value}
|
||||
>
|
||||
TODO: List columns
|
||||
${this._itemColumns.map(
|
||||
(itemColumn) => html`<p>${itemColumn.displayName}: (${itemColumn.name}:${itemColumn.type})</p>`
|
||||
)}
|
||||
TODO: Allow choosing field type per column TODO: Re-parse data with new columns after choosing field
|
||||
types
|
||||
<ul>
|
||||
${this._itemColumns.map(
|
||||
(itemColumn, itemColumnIndex) => html`
|
||||
<li class="margined">
|
||||
<pl-select
|
||||
id=${`itemColumnSelect-${itemColumnIndex}`}
|
||||
icon=${FIELD_DEFS[itemColumn.type].icon}
|
||||
.label=${itemColumn.displayName}
|
||||
.options=${fieldTypeOptions}
|
||||
.value=${itemColumn.type}
|
||||
@change=${() => {
|
||||
const thisElement = this.shadowRoot?.querySelector(
|
||||
`#itemColumnSelect-${itemColumnIndex}`
|
||||
) as HTMLSelectElement;
|
||||
const newValue = thisElement?.value as FieldType;
|
||||
if (newValue) {
|
||||
this._itemColumns[itemColumnIndex].type = newValue;
|
||||
this._parseData();
|
||||
}
|
||||
}}
|
||||
></pl-select>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<pl-select
|
||||
|
@ -137,8 +164,9 @@ Github,"work,coding",https://github.com,john.doe@gmail.com,129lskdf93`)
|
|||
this._items = await imp.asLastPass(file);
|
||||
break;
|
||||
case imp.CSV.value:
|
||||
this._itemColumns = await imp.asCSVColumns(file);
|
||||
this._items = await imp.asCSV(file);
|
||||
const result = await imp.asCSV(file, this._itemColumns);
|
||||
this._items = result.items;
|
||||
this._itemColumns = result.itemColumns;
|
||||
break;
|
||||
case imp.ONEPUX.value:
|
||||
this._items = await imp.as1Pux(file);
|
||||
|
|
|
@ -57,11 +57,17 @@ export function loadPapa(): Promise<any> {
|
|||
* @param Array data Two-dimensional array containing tabular item data; The first 'row'
|
||||
* should contain field names. All other rows represent items, containing
|
||||
* the item name, field values and optionally a list of tags.
|
||||
* @param Array columnTypes Array containing the type of field per column.
|
||||
* @param Integer nameColIndex Index of the column containing the item names. Defaults to 0
|
||||
* @param Integer tagsColIndex Index of the column containing the item categories. If left empty
|
||||
* no categories will be used
|
||||
*/
|
||||
export async function fromTable(data: string[][], nameColIndex?: number, tagsColIndex?: number): Promise<VaultItem[]> {
|
||||
async function fromTable(
|
||||
data: string[][],
|
||||
columnTypes: ImportCSVColumn[],
|
||||
nameColIndex?: number,
|
||||
tagsColIndex?: number
|
||||
): Promise<VaultItem[]> {
|
||||
// Use first row for column names
|
||||
const colNames = data[0];
|
||||
|
||||
|
@ -86,10 +92,12 @@ export async function fromTable(data: string[][], nameColIndex?: number, tagsCol
|
|||
if (i != nameColIndex && i != tagsColIndex && row[i]) {
|
||||
const name = colNames[i];
|
||||
const value = row[i];
|
||||
const type = columnTypes[i].type || undefined;
|
||||
fields.push(
|
||||
new Field().fromRaw({
|
||||
name,
|
||||
value,
|
||||
type,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -108,17 +116,12 @@ export async function isCSV(data: string): Promise<Boolean> {
|
|||
return papa.parse(data).errors.length === 0;
|
||||
}
|
||||
|
||||
export async function asCSV(file: File, nameColIndex?: number, tagsColIndex?: number): Promise<VaultItem[]> {
|
||||
const data = await readFileAsText(file);
|
||||
const papa = await loadPapa();
|
||||
const parsed = papa.parse(data);
|
||||
if (parsed.errors.length) {
|
||||
throw new Err(ErrorCode.INVALID_CSV, "Failed to parse .csv file.");
|
||||
}
|
||||
return fromTable(parsed.data, nameColIndex, tagsColIndex);
|
||||
}
|
||||
|
||||
export async function asCSVColumns(file: File): Promise<ImportCSVColumn[]> {
|
||||
export async function asCSV(
|
||||
file: File,
|
||||
mappedItemColumns: ImportCSVColumn[],
|
||||
nameColIndex?: number,
|
||||
tagsColIndex?: number
|
||||
): Promise<{ items: VaultItem[]; itemColumns: ImportCSVColumn[] }> {
|
||||
const data = await readFileAsText(file);
|
||||
const papa = await loadPapa();
|
||||
const parsed = papa.parse(data);
|
||||
|
@ -127,20 +130,29 @@ export async function asCSVColumns(file: File): Promise<ImportCSVColumn[]> {
|
|||
}
|
||||
const rows = parsed.data as string[][];
|
||||
|
||||
const columnNames = rows[0].map((column) => column.toLowerCase());
|
||||
const columnNames = rows.length > 0 ? rows[0].map((column) => column.toLowerCase()) : [];
|
||||
|
||||
return columnNames.map((columnName) => {
|
||||
const type = (Object.keys(FIELD_DEFS).filter((fieldType) => columnName.includes(fieldType))[0] ||
|
||||
FieldType.Text) as FieldType;
|
||||
const itemColumns =
|
||||
mappedItemColumns.length > 0
|
||||
? mappedItemColumns
|
||||
: columnNames.map((columnName) => {
|
||||
// TODO: Use guessFieldType instead, after improving it
|
||||
const type = (Object.keys(FIELD_DEFS).filter((fieldType) => columnName.includes(fieldType))[0] ||
|
||||
FieldType.Text) as FieldType;
|
||||
|
||||
return {
|
||||
name: columnName,
|
||||
displayName: capitalize(columnName),
|
||||
type,
|
||||
};
|
||||
});
|
||||
// TODO: Also guess "Name" and "Tags"
|
||||
|
||||
return {
|
||||
name: columnName,
|
||||
displayName: capitalize(columnName),
|
||||
type,
|
||||
};
|
||||
});
|
||||
|
||||
const items = await fromTable(rows, itemColumns, nameColIndex, tagsColIndex);
|
||||
|
||||
return { items, itemColumns };
|
||||
}
|
||||
|
||||
export async function isPadlockV1(file: File): Promise<boolean> {
|
||||
try {
|
||||
const data = await readFileAsText(file);
|
||||
|
|
Loading…
Reference in New Issue