SecureNotes/src/components/routes/vaults/Vaults.tsx

294 lines
8.7 KiB
TypeScript

import { h } from "preact";
import { Page } from "../../../page";
import Notes, { VaultList } from "../../../notes";
import "./vaults.scss";
import { Lock, Unlock, Settings } from "preact-feather";
import Navigation from "../../../navigation";
import { InputModal } from "../../modals/InputModal";
import { YesNoModal } from "../../modals/YesNoModal";
import AddButton from "../../AddButton";
import ContextMenu from "../../context";
import Notifications from "../../../notifications";
export interface VaultsProps {
state: any;
selectVault?: boolean;
onSelected?: (vaultid: string) => void;
}
export default class VaultsPage extends Page<
VaultsProps,
{
vaults: VaultList;
modal: h.JSX.Element | undefined;
context: h.JSX.Element | undefined;
}
> {
constructor(props: VaultsProps) {
super(props);
this.state = { vaults: [], modal: undefined, context: undefined };
this.updateVaults = this.updateVaults.bind(this);
}
updateVaults(s?: boolean) {
if (s) return;
return new Promise((yes) => {
Notes.getVaults().then((vaults) => this.setState({ vaults }, yes));
});
}
componentWillMount() {
this.updateVaults();
Notes.syncObservable.subscribe(this.updateVaults);
}
componentWillUnmount() {
Notes.syncObservable.unsubscribe(this.updateVaults);
}
async getKey(vault: { name: string; id: string }, permanent = true) {
let inp_mod = new InputModal(
"Enter password for " + vault.name,
"Password",
"password"
);
let key = undefined;
while (true) {
// inp_mod.show();
let value = await inp_mod.getResult(false);
if (value === null) {
console.log("Value is null");
inp_mod.close();
return false;
} else {
key = Notes.passwordToKey(value);
try {
await Notes.getVault(vault.id, key);
break;
} catch (err) {
Notifications.sendError("Invalid password!");
}
}
}
inp_mod.close();
let perm = false;
if (permanent) {
let save_modal = new YesNoModal("Save permanent?");
let res = await save_modal.getResult();
if (res === undefined) {
res = false;
}
perm = res;
}
Notes.saveVaultKey(vault.id, key, perm);
return true;
}
async openVault(vault: { name: string; encrypted: boolean; id: string }) {
const action = () => {
if (this.props.selectVault) {
this.props.onSelected(vault.id);
} else {
Navigation.setPage("/vault", { id: vault.id });
}
};
if (vault.encrypted) {
let key = Notes.getVaultKey(vault.id);
if (key) action();
else {
if (await this.getKey(vault)) action();
}
} else {
action();
}
}
async addButtonClick() {
let name_modal = new InputModal("Enter new name", "Name", "text");
let name = await name_modal.getResult();
if (name === null) return;
let encrypted_modal = new YesNoModal("Encrypt?");
let encrypted = encrypted_modal.getResult();
if (encrypted === null) return;
let password;
if (encrypted) {
let password_modal = new InputModal(
"Enter new password",
"Password",
"password"
);
password = await password_modal.getResult();
if (password === null) return;
}
let key;
if (password) {
key = Notes.passwordToKey(password);
}
await Notes.createVault(name, key);
this.updateVaults();
}
onContext(
evt: MouseEvent,
vault: { name: string; encrypted: boolean; id: string }
) {
evt.preventDefault();
evt.stopPropagation();
const close = () => {
document.documentElement.removeEventListener("click", close);
this.setState({ context: undefined });
};
document.documentElement.addEventListener("click", close);
let deleteb = (
<button
class="btn"
onClick={async () => {
let delete_modal = new YesNoModal(
"Delete Vault? Cannot be undone!"
);
let result = await delete_modal.getResult();
if (result) {
Notes.deleteVault(vault.id)
.then(() => {
this.updateVaults();
})
.catch((err) => {
Notifications.sendError("Error deleting vault!");
console.error(err);
});
}
}}
>
delete
</button>
);
let delete_key;
if (Notes.getVaultKey(vault.id)) {
delete_key = (
<button
class="btn"
onClick={() => {
Notes.forgetVaultKey(vault.id);
Notifications.sendSuccess("Forgot password!");
}}
>
forget password
</button>
);
}
let exportb = (
<button
class="btn"
onClick={async () => {
let key: Uint8Array;
if (vault.encrypted) {
await this.getKey(vault, false);
key = Notes.getVaultKey(vault.id);
}
let note_vault = await Notes.getVault(vault.id, key);
let base_notes = await note_vault.getAllNotes();
let notes = await Promise.all(
base_notes.map((e) => {
return note_vault.getNote(e._id);
})
);
let result = {
version: 1,
notes: notes.map((e) => {
return {
content: e.__value,
time: e.time,
};
}),
};
var dataStr =
"data:text/json;charset=utf-8," +
encodeURIComponent(JSON.stringify(result, undefined, 3));
var downloadAnchorNode = document.createElement("a");
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute(
"download",
"notes_export_" + vault.name + ".json"
);
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
}}
>
export
</button>
);
let context = (
<ContextMenu event={evt}>
{deleteb}
{delete_key}
{exportb}
</ContextMenu>
);
this.setState({ context });
return false;
}
render() {
let elms = this.state.vaults.map((vault) => {
return (
<li
class="vaults_vault"
onClick={() => this.openVault(vault)}
onContextMenu={(evt) => this.onContext(evt, vault)}
>
{vault.encrypted ? (
<Lock height={undefined} width={undefined} />
) : (
<Unlock height={undefined} width={undefined} />
)}
<span>{vault.name}</span>
</li>
);
});
return (
<div style={{ marginTop: "-12px", paddingTop: "12px" }}>
{/* {this.state.modal} */}
{this.state.context}
<header class="header">
<span></span>
<h3
style="display:inline"
onClick={() => Navigation.setPage("/")}
>
{this.props.selectVault
? "Select Vault for share"
: "Your vaults:"}
</h3>
<a
class="header-icon-button"
onClick={() => Navigation.setPage("/settings")}
>
<Settings height={undefined} width={undefined} />
</a>
</header>
<AddButton onClick={() => this.addButtonClick()} />
<div class="container">
<ul class="list list-divider list-clickable">{elms}</ul>
</div>
</div>
);
}
}