First Alpha

This commit is contained in:
Fabian
2019-03-24 21:25:08 -04:00
parent 3ef36ab6ca
commit 8b6c71247f
30 changed files with 636 additions and 478 deletions

View File

@ -1,7 +1,6 @@
import { h } from "preact";
import "./add_button.scss";
import Plus from "feather-icons/dist/icons/plus.svg";
export default function AddButton({ onClick }: { onClick: () => void }) {
return <button title={"add button"} class="add_button_button circular primary shadowed" onClick={() => onClick()}><Plus width={undefined} height={undefined} /></button>
}

View File

@ -2,11 +2,13 @@ import { Router } from "./Routing";
import { h } from "preact";
import NotificationsComponent from "./notifications";
import { ModalComponent } from "./modals/Modal";
import { Footer } from "./Footer";
export default function App() {
return <div>
<ModalComponent />
<NotificationsComponent />
<Router />
<Footer />
</div>
}

70
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,70 @@
import { h, Component } from "preact";
import Notes from "../notes";
import Refresh from "feather-icons/dist/icons/refresh-cw.svg";
import "./footer.scss";
import Notifications from "../notifications";
export class Footer extends Component<{}, { synced: boolean, syncing: boolean }> {
constructor(props) {
super(props);
this.state = { synced: false, syncing: false };
this.onSyncedChange = this.onSyncedChange.bind(this);
this.onSyncChange = this.onSyncChange.bind(this);
}
async componentWillMount() {
Notes.syncedObservable.subscribe(this.onSyncedChange);
Notes.syncObservable.subscribe(this.onSyncChange);
this.setState({ synced: await Notes.isSynced() })
}
componentWillUnmount() {
Notes.syncedObservable.unsubscribe(this.onSyncedChange);
Notes.syncObservable.unsubscribe(this.onSyncChange);
}
onSyncChange(state: boolean) {
console.log("sync", state);
this.setState({ syncing: state })
}
onSyncedChange(state: boolean) {
console.log("synced", state);
this.setState({ synced: state })
}
onSyncClick() {
Notes.sync().then(() => {
Notifications.sendInfo("Finished Synchronisation");
})
}
render() {
let extrac = "";
let color;
let text;
if (this.state.syncing) {
color = "orange";
text = "syncing";
extrac = "reloading"
} else {
if (this.state.synced) {
color = "green";
text = "synced";
} else {
color = "red";
text = "not synced";
}
}
return <footer>
<span>
<span class={extrac} ><a onClick={() => this.onSyncClick()} ><Refresh style="height: 1em; width: 1em;"></Refresh></a></span>
<span style={"margin-left: 8px; color:" + color}>{text}</span>
</span>
<span>
Welcome <b>{Notes.name}</b>
</span>
</footer>
}
}

View File

@ -12,14 +12,14 @@ export class Router extends Component<{}, { next?: JSX.Element, current: JSX.Ele
}
componentWillMount() {
Navigation.pageObservable.subscribe(this.onChange, true)
Navigation.pageObservable.subscribe(this.onChange)
}
componentWillUnmount() {
Navigation.pageObservable.unsubscribe(this.onChange)
}
onChange([page]: JSX.Element[]) {
onChange(page: JSX.Element) {
this.setState({ next: page, current: this.state.next || this.state.current });
}
@ -42,7 +42,7 @@ export class Router extends Component<{}, { next?: JSX.Element, current: JSX.Ele
{this.state.next}
</div>
}
return <div style="overflow:hidden; width: 1vw;">
return <div style="position: relative; overflow-x: hidden; width: 100vw; height: 100vh;">
<div class="transition_container" key={this.state.current.key} ref={elm => this.mounted = elm}>
{this.state.current}
</div>

View File

@ -0,0 +1,19 @@
.reloading { animation: turner 1s infinite linear }
@keyframes turner{
from{ transform: rotate(0deg) }
to { transform: rotate(360deg) }
}
footer {
z-index: 128;
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: calc(0.9 * var(--universal-padding)) !important;
}

View File

@ -23,7 +23,7 @@ export class InputModal extends Modal<string> {
render() {
return <Modal.BaseModal modal={this.props.modal}>
<fieldset style="border:none;">
<fieldset style="border:none; min-inline-size:0;">
<label for={this.rand}>{this.props.modal.fieldname}</label>
<input style="min-width: 85%" autofocus ref={elm => {
this.input = elm
@ -47,41 +47,4 @@ export class InputModal extends Modal<string> {
getComponent() {
return <InputModal.IMD modal={this} />
}
}
// export class InputModal extends Component<{ title: string, fieldname: string, type: "text" | "password", onResult: (result) => void }, {}> {
// input: HTMLInputElement;
// rand: string;
// constructor(props) {
// super(props);
// this.rand = Math.random().toString();
// }
// componentWillUnmount() {
// if (this.input)
// this.input.value = "";
// }
// render() {
// return <Modal title={this.props.title} onClose={() => this.props.onResult(null)}>
// <fieldset style="border:none;">
// <label for={this.rand}>{this.props.fieldname}</label>
// <input style="min-width: 85%" autofocus ref={elm => {
// this.input = elm
// if (this.input)
// setTimeout(() => this.input.focus(), 0)
// }} type={this.props.type} id={this.rand} placeholder={this.props.fieldname} onKeyDown={evt => {
// if (evt.keyCode === 13) {
// this.props.onResult(this.input.value)
// }
// }} />
// <div style="text-align: right;">
// <button class="primary" style="display: inline-block;" onClick={() => {
// this.props.onResult(this.input.value);
// }}>Enter</button>
// </div>
// </fieldset>
// </Modal>
// }
// }
}

View File

@ -1,9 +1,10 @@
import Observable from "../../helper/observable";
import { Observable } from "@hibas123/utils";
import { h, Component } from "preact";
import CloseIcon from "feather-icons/dist/icons/x.svg";
export default abstract class Modal<T> {
// Static
private static modalObservableServer = new Observable<{ modal: Modal<any>, close: boolean }>(false);
private static modalObservableServer = new Observable<{ modal: Modal<any>, close: boolean }>();
public static modalObservable = Modal.modalObservableServer.getPublicApi();
@ -11,9 +12,12 @@ export default abstract class Modal<T> {
// Private
private onResult: (result: T | null) => void;
private closeOnResult: boolean;
// Protected
protected result(value: T | null) {
if (this.closeOnResult)
this.close()
if (this.onResult)
this.onResult(value);
}
@ -32,7 +36,8 @@ export default abstract class Modal<T> {
*
* Call close when successful
*/
public async getResult() {
public async getResult(close = true) {
this.closeOnResult = close;
this.show();
return new Promise<T | null>((yes) => this.onResult = yes);
}
@ -65,7 +70,12 @@ export default abstract class Modal<T> {
}
}}>
<div class="card" >
<h3 class="section">{this.props.modal.title}</h3>
<div class="section" style="display:flex;justify-content:space-between;">
<h3>{this.props.modal.title}</h3>
<h3>
<CloseIcon onClick={() => this.props.modal.result(null)} width={undefined} height={undefined} style="height:calc(1rem * var(--heading-ratio) * var(--heading-ratio))" />
</h3>
</div>
{this.props.children}
</div>
</div>
@ -74,62 +84,24 @@ export default abstract class Modal<T> {
}
// export default abstract class Modal<T, S> extends Component<{}, S> {
// // Abstract
// protected abstract renderChilds(): JSX.Element;
// protected abstract title: string;
// render() {
// return <div class="modal_container" onClick={(evt) => {
// let path = evt.composedPath();
// if (!path.find(e => {
// let res = false;
// let s = (e as Element);
// if (s) {
// if (s.classList) {
// res = s.classList.contains("card")
// }
// }
// return res;
// })) {
// this.result(null)
// }
// }} onKeyDown={evt => {
// if (evt.keyCode === 27) {
// this.result(null)
// }
// }}>
// <div class="card" >
// <h3 class="section">{this.title}</h3>
// {this.renderChilds()}
// </div>
// </div>
// }
// }
export class ModalComponent extends Component<{}, { modal: Modal<any> | undefined, component: JSX.Element | undefined }>{
constructor(props) {
super(props);
this.onModal = this.onModal.bind(this);
}
onModal([{ modal, close }]: { modal: Modal<any>, close: boolean }[]) {
onModal({ modal, close }: { modal: Modal<any>, close: boolean }) {
if (!close && this.state.modal !== modal) {
this.setState({ modal: modal, component: modal.getComponent() })
}
else {
if (this.state.modal === modal) // Only close if the same
if (this.state.modal === modal && close) // Only close if the same
this.setState({ modal: undefined, component: undefined })
}
}
componentWillMount() {
Modal.modalObservable.subscribe(this.onModal, true);
Modal.modalObservable.subscribe(this.onModal);
}
componentWillUnmount() {

View File

@ -28,7 +28,7 @@ export class YesNoModal extends Modal<boolean> {
render() {
return <Modal.BaseModal modal={this.props.modal}>
<fieldset style="border:none;">
<fieldset style="border:none;min-inline-size:0;">
<div style="text-align: right;">
<button class="primary" style="display: inline-block;" onClick={() => {
this.props.modal.result(false);

View File

@ -1,9 +1,20 @@
:root {
--notification-margin: 15px;
}
@media screen and (min-width: 1000px) {
:root {
--notification-margin: 35px;
}
}
.notifications_container {
position: fixed;
top: 0;
right: 0;
z-index: 128;
margin-top: 35px;
margin-top: calc(var(--notification-margin) - 10px);
}
.notifications_success {
@ -30,6 +41,7 @@
.notifications_notification {
h4 {
color: inherit;
font-weight: bold;
margin: 0;
}
@ -41,7 +53,7 @@
position: static;
margin-top:10px;
margin-right:35px;
margin-right:var(--notification-margin);
padding: 0.5em;
width: 300px;
}

View File

@ -30,7 +30,7 @@ export default class NotificationsComponent extends Component<{}, {
Notifications.messageObservable.unsubscribe(this.onNotification);
}
onNotification([not]: { message: string, type: MessageType }[]) {
onNotification(not: { message: string, type: MessageType }) {
console.log("Got notification", not)
let n = this.state.notifications.slice(0);
n.push(not);

View File

@ -31,7 +31,8 @@ export default class EntryComponent extends Component<{ vault: Promise<IVault>,
private toVault() {
// history.back()
Navigation.setPage("/vault", { id: this.vault.id }, { entry: "false" }, true);
history.back();
// Navigation.setPage("/vault", { id: this.vault.id }, { entry: "false" }, true);
}
async componentWillMount() {
@ -128,7 +129,6 @@ export default class EntryComponent extends Component<{ vault: Promise<IVault>,
if (this.state.changed) {
let modal = new YesNoModal("Really want to quit?");
let res = await modal.getResult();
modal.close();
if (res === true) {
this.skip_save = true;
this.toVault();
@ -138,7 +138,6 @@ export default class EntryComponent extends Component<{ vault: Promise<IVault>,
}
textAreaKeyPress(evt: KeyboardEvent) {
console.log(evt);
if ((evt.keyCode === 83 || evt.keyCode === 13) && evt.ctrlKey) {
evt.preventDefault()
this.save();

View File

@ -3,7 +3,6 @@ import Notes, { IVault, BaseNote } from "../../../notes";
import AddButton from "../../AddButton";
import Navigation from "../../../navigation";
import ArrowLeft from "feather-icons/dist/icons/arrow-left.svg"
import MoreVertival from "feather-icons/dist/icons/more-vertical.svg"
import ContextMenu from "../../modals/context";
import Notifications from "../../../notifications";
@ -17,7 +16,9 @@ export default class EntryList extends Component<{ vault: Promise<IVault> }, { n
}
vault: IVault;
reloadNotes() {
reloadNotes(s?: boolean) {
if (s)
return;
return new Promise(yes => this.vault.getAllNotes().then(entries => this.setState({ notes: entries }, yes)));
}
@ -169,7 +170,6 @@ export default class EntryList extends Component<{ vault: Promise<IVault> }, { n
<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">
<h1 class="section double-padded">Notes: </h1>
<div class="section">
{elms}
</div>

View File

@ -7,6 +7,7 @@ import EntryList from "./EntryList";
import "./vault.scss"
export interface VaultProps {
state: { id: string };
hidden: { entry: string, id: string, note: string };
@ -21,10 +22,19 @@ export default class VaultPage extends Page<VaultProps, { entries: BaseNote[] }>
async componentWillMount() {
this.vault = Notes.getVault(this.props.state.id, Notes.getVaultKey(this.props.state.id))
this.vault.then(vlt => {
window.debug.activeVault = vlt;
window.debug.createNotes = (cnt = 10) => {
for (let i = 0; i < cnt; i++) {
let nt = vlt.newNote();
nt.__value = `Random Note ${i}\ With some Content ${i}`;
vlt.saveNote(nt);
}
}
})
this.vault.catch(err => {
Navigation.setPage("/")
})
});
}
render() {

View File

@ -24,7 +24,9 @@ export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, m
this.updateVaults = this.updateVaults.bind(this);
}
updateVaults() {
updateVaults(s?: boolean) {
if (s)
return;
return new Promise(yes => {
Notes.getVaults().then(vaults => this.setState({ vaults }, yes))
})
@ -43,9 +45,11 @@ export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, m
let inp_mod = new InputModal("Enter password for " + vault.name, "Password", "password");
let key = undefined;
while (true) {
let value = await inp_mod.getResult();
// inp_mod.show();
let value = await inp_mod.getResult(false);
if (value === null) {
inp_mod.close();
console.log("Value is null")
inp_mod.close()
return false;
} else {
key = Notes.passwordToKey(value);
@ -57,13 +61,12 @@ export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, m
}
}
}
inp_mod.close();
inp_mod.close()
let perm = false;
if (permanent) {
let save_modal = new YesNoModal("Save permanent?");
let res = await save_modal.getResult();
save_modal.close();
if (res === undefined) {
res = false;
}
@ -101,19 +104,16 @@ export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, m
async addButtonClick() {
let name_modal = new InputModal("Enter new name", "Name", "text");
let name = await name_modal.getResult();
name_modal.close();
if (name === null) return;
let encrypted_modal = new YesNoModal("Encrypt?");
let encrypted = encrypted_modal.getResult();
encrypted_modal.close();
if (encrypted === null) return;
let password;
if (encrypted) {
let password_modal = new InputModal("Enter new password", "Password", "password");
password = await password_modal.getResult();
password_modal.close();
if (password === null) return;
}
@ -127,11 +127,6 @@ export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, m
onContext(evt: MouseEvent, vault: { name: string, encrypted: boolean, id: string }) {
evt.preventDefault();
console.log("Context", evt);
// let context = <div style={{ position: "fixed", left: evt.pageX, top: evt.pageY, zIndex: 10 }}>
// <button>Action 1</button>
// </div>
const close = () => {
window.removeEventListener("click", close);
@ -142,7 +137,6 @@ export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, m
let deleteb = <button onClick={async () => {
let delete_modal = new YesNoModal("Delete Vault? Cannot be undone!");
let result = await delete_modal.getResult();
delete_modal.close();
if (result) {
Notes.deleteVault(vault.id).then(() => {
this.updateVaults();
@ -157,7 +151,10 @@ export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, m
let delete_key;
if (Notes.getVaultKey(vault.id)) {
delete_key = <button onClick={() => { Notes.forgetVaultKey(vault.id); }}>
delete_key = <button onClick={() => {
Notes.forgetVaultKey(vault.id);
Notifications.sendSuccess("Forgot key!")
}}>
forget password
</button>;
}
@ -219,14 +216,16 @@ export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, m
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">
<h1 class="section double-padded">Your vaults:</h1>
<div class="section">
{elms}
</div>

View File

@ -6,9 +6,13 @@
width: 100%;
overflow: auto;
background: var(--back-color);
padding-bottom: 40px;
}
.transition_slidein {
position: absolute;
top: 0;
left: 0;
animation-name: slidein;
animation-duration: 0.3s;
z-index: 64;