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

238 lines
7.5 KiB
TypeScript

import { h } from "preact"
import { Page } from "../../../page";
import Notes, { VaultList } from "../../../notes";
import "./vaults.scss"
import Lock from "feather-icons/dist/icons/lock.svg";
import Unlock from "feather-icons/dist/icons/unlock.svg";
import Navigation from "../../../navigation";
import { InputModal } from "../../modals/InputModal";
import { YesNoModal } from "../../modals/YesNoModal";
import AddButton from "../../AddButton";
import ContextMenu from "../../modals/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: JSX.Element | undefined, context: 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();
const close = () => {
window.removeEventListener("click", close);
this.setState({ context: undefined });
}
window.addEventListener("click", close);
let deleteb = <button 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 onClick={() => {
Notes.forgetVaultKey(vault.id);
Notifications.sendSuccess("Forgot key!")
}}>
forget password
</button>;
}
let exportb = <button 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 <div 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>
</div>
})
return <div style={{ marginTop: "-12px", paddingTop: "12px" }} >
{this.state.modal}
{this.state.context}
<header>
<span></span>
<h1 style="display:inline" class="button header_title" onClick={() => Navigation.setPage("/")}>Your vaults:</h1>
<span></span>
</header>
<AddButton onClick={() => this.addButtonClick()} />
<div class="container">
<div class="row">
<div class="col-sm-12 col-md-8 col-lg-6 col-md-offset-2 col-lg-offset-3">
<div class="card fluid">
<div class="section">
{elms}
</div>
</div>
</div>
</div>
</div>
</div>
}
}