feat: search options (#540)

* feat: search options

* feat: sanitize folder names

* fix: add cursor: pointer to switch

* fix: explicitly make the search bar a text input

* refactor: remove magic number

* refactor: extract Switch component to its own file

* refactor: split AppState into multiple files

* refactor: review comments
This commit is contained in:
Baptiste Grob
2021-04-06 16:48:25 +02:00
committed by GitHub
parent 275c8cbd1f
commit ed69680295
24 changed files with 672 additions and 319 deletions

View File

@@ -0,0 +1,21 @@
import { action, makeObservable, observable } from "mobx";
export class AccountMenuState {
show = false;
constructor() {
makeObservable(this, {
show: observable,
setShow: action,
toggleShow: action,
});
}
setShow = (show: boolean): void => {
this.show = show;
}
toggleShow = (): void => {
this.show = !this.show;
}
}

View File

@@ -0,0 +1,22 @@
import { UuidString } from "@standardnotes/snjs";
import { action, makeObservable, observable } from "mobx";
export class ActionsMenuState {
hiddenExtensions: Record<UuidString, boolean> = {};
constructor() {
makeObservable(this, {
hiddenExtensions: observable,
toggleExtensionVisibility: action,
reset: action,
});
}
toggleExtensionVisibility = (uuid: UuidString): void => {
this.hiddenExtensions[uuid] = !this.hiddenExtensions[uuid];
}
reset = (): void => {
this.hiddenExtensions = {};
}
}

View File

@@ -7,16 +7,18 @@ import {
ContentType,
PayloadSource,
DeinitSource,
UuidString,
SyncOpStatus,
PrefKey,
SNApplication,
} from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application';
import { Editor } from '@/ui_models/editor';
import { action, makeObservable, observable, runInAction } from 'mobx';
import { action, makeObservable, observable } from 'mobx';
import { Bridge } from '@/services/bridge';
import { storage, StorageKey } from '@/services/localStorage';
import { AccountMenuState } from './account_menu_state';
import { ActionsMenuState } from './actions_menu_state';
import { NoAccountWarningState } from './no_account_warning_state';
import { SyncState } from './sync_state';
import { SearchOptionsState } from './search_options_state';
export enum AppStateEvent {
TagChanged,
@@ -41,113 +43,6 @@ export enum EventSource {
type ObserverCallback = (event: AppStateEvent, data?: any) => Promise<void>;
class ActionsMenuState {
hiddenExtensions: Record<UuidString, boolean> = {};
constructor() {
makeObservable(this, {
hiddenExtensions: observable,
toggleExtensionVisibility: action,
reset: action,
});
}
toggleExtensionVisibility(uuid: UuidString) {
this.hiddenExtensions[uuid] = !this.hiddenExtensions[uuid];
}
reset() {
this.hiddenExtensions = {};
}
}
export class SyncState {
inProgress = false;
errorMessage?: string = undefined;
humanReadablePercentage?: string = undefined;
constructor() {
makeObservable(this, {
inProgress: observable,
errorMessage: observable,
humanReadablePercentage: observable,
update: action,
});
}
update(status: SyncOpStatus): void {
this.errorMessage = status.error?.message;
this.inProgress = status.syncInProgress;
const stats = status.getStats();
const completionPercentage =
stats.uploadCompletionCount === 0
? 0
: stats.uploadCompletionCount / stats.uploadTotalCount;
if (completionPercentage === 0) {
this.humanReadablePercentage = undefined;
} else {
this.humanReadablePercentage = completionPercentage.toLocaleString(
undefined,
{ style: 'percent' }
);
}
}
}
class AccountMenuState {
show = false;
constructor() {
makeObservable(this, {
show: observable,
setShow: action,
toggleShow: action,
});
}
setShow(show: boolean) {
this.show = show;
}
toggleShow() {
this.show = !this.show;
}
}
class NoAccountWarningState {
show: boolean;
constructor(application: SNApplication, appObservers: (() => void)[]) {
this.show = application.hasAccount()
? false
: storage.get(StorageKey.ShowNoAccountWarning) ?? true;
appObservers.push(
application.addEventObserver(async () => {
runInAction(() => {
this.show = false;
});
}, ApplicationEvent.SignedIn),
application.addEventObserver(async () => {
if (application.hasAccount()) {
runInAction(() => {
this.show = false;
});
}
}, ApplicationEvent.Started)
);
makeObservable(this, {
show: observable,
hide: action,
});
}
hide() {
this.show = false;
storage.set(StorageKey.ShowNoAccountWarning, false);
}
reset() {
storage.remove(StorageKey.ShowNoAccountWarning);
}
}
export class AppState {
readonly enableUnfinishedFeatures =
isDev || location.host.includes('app-dev.standardnotes.org');
@@ -167,8 +62,8 @@ export class AppState {
readonly actionsMenu = new ActionsMenuState();
readonly noAccountWarning: NoAccountWarningState;
readonly sync = new SyncState();
readonly searchOptions;
isSessionsModalVisible = false;
mouseUp = Promise.resolve();
private appEventObserverRemovers: (() => void)[] = [];
@@ -186,6 +81,10 @@ export class AppState {
application,
this.appEventObserverRemovers
);
this.searchOptions = new SearchOptionsState(
application,
this.appEventObserverRemovers
);
this.addAppEventObserver();
this.streamNotesAndTags();
this.onVisibilityChange = () => {
@@ -196,7 +95,6 @@ export class AppState {
this.notifyEvent(event);
};
this.registerVisibilityObservers();
document.addEventListener('mousedown', this.onMouseDown);
if (this.bridge.appVersion.includes('-beta')) {
this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true;
@@ -233,16 +131,9 @@ export class AppState {
this.rootScopeCleanup2 = undefined;
}
document.removeEventListener('visibilitychange', this.onVisibilityChange);
document.removeEventListener('mousedown', this.onMouseDown);
this.onVisibilityChange = undefined;
}
onMouseDown = (): void => {
this.mouseUp = new Promise((resolve) => {
document.addEventListener('mouseup', () => resolve(), { once: true });
});
};
openSessionsModal() {
this.isSessionsModalVisible = true;
}

View File

@@ -0,0 +1,6 @@
export {
AppState,
AppStateEvent,
EventSource,
PanelResizedData,
} from './app_state';

View File

@@ -0,0 +1,41 @@
import { storage, StorageKey } from "@/services/localStorage";
import { SNApplication, ApplicationEvent } from "@standardnotes/snjs";
import { runInAction, makeObservable, observable, action } from "mobx";
export class NoAccountWarningState {
show: boolean;
constructor(application: SNApplication, appObservers: (() => void)[]) {
this.show = application.hasAccount()
? false
: storage.get(StorageKey.ShowNoAccountWarning) ?? true;
appObservers.push(
application.addEventObserver(async () => {
runInAction(() => {
this.show = false;
});
}, ApplicationEvent.SignedIn),
application.addEventObserver(async () => {
if (application.hasAccount()) {
runInAction(() => {
this.show = false;
});
}
}, ApplicationEvent.Started)
);
makeObservable(this, {
show: observable,
hide: action,
});
}
hide = (): void => {
this.show = false;
storage.set(StorageKey.ShowNoAccountWarning, false);
}
reset = (): void => {
storage.remove(StorageKey.ShowNoAccountWarning);
}
}

View File

@@ -0,0 +1,59 @@
import { ApplicationEvent } from "@standardnotes/snjs";
import { makeObservable, observable, action, runInAction } from "mobx";
import { WebApplication } from "../application";
export class SearchOptionsState {
includeProtectedContents = false;
includeArchived = false;
includeTrashed = false;
constructor(
private application: WebApplication,
appObservers: (() => void)[]
) {
makeObservable(this, {
includeProtectedContents: observable,
includeTrashed: observable,
includeArchived: observable,
toggleIncludeArchived: action,
toggleIncludeTrashed: action,
toggleIncludeProtectedContents: action,
refreshIncludeProtectedContents: action,
});
appObservers.push(
this.application.addEventObserver(async () => {
this.refreshIncludeProtectedContents();
}, ApplicationEvent.ProtectionSessionExpiryDateChanged)
);
}
toggleIncludeArchived = (): void => {
this.includeArchived = !this.includeArchived;
};
toggleIncludeTrashed = (): void => {
this.includeTrashed = !this.includeTrashed;
};
refreshIncludeProtectedContents = (): void => {
if (
this.includeProtectedContents &&
this.application.areProtectionsEnabled()
) {
this.includeProtectedContents = false;
}
};
toggleIncludeProtectedContents = async (): Promise<void> => {
if (this.includeProtectedContents) {
this.includeProtectedContents = false;
} else {
const authorized = await this.application.authorizeSearchingProtectedNotesText();
runInAction(() => {
this.includeProtectedContents = authorized;
});
}
};
}

View File

@@ -0,0 +1,36 @@
import { SyncOpStatus } from "@standardnotes/snjs";
import { action, makeObservable, observable } from "mobx";
export class SyncState {
inProgress = false;
errorMessage?: string = undefined;
humanReadablePercentage?: string = undefined;
constructor() {
makeObservable(this, {
inProgress: observable,
errorMessage: observable,
humanReadablePercentage: observable,
update: action,
});
}
update = (status: SyncOpStatus): void => {
this.errorMessage = status.error?.message;
this.inProgress = status.syncInProgress;
const stats = status.getStats();
const completionPercentage =
stats.uploadCompletionCount === 0
? 0
: stats.uploadCompletionCount / stats.uploadTotalCount;
if (completionPercentage === 0) {
this.humanReadablePercentage = undefined;
} else {
this.humanReadablePercentage = completionPercentage.toLocaleString(
undefined,
{ style: 'percent' }
);
}
}
}