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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 = {};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
6
app/assets/javascripts/ui_models/app_state/index.ts
Normal file
6
app/assets/javascripts/ui_models/app_state/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
AppState,
|
||||
AppStateEvent,
|
||||
EventSource,
|
||||
PanelResizedData,
|
||||
} from './app_state';
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
36
app/assets/javascripts/ui_models/app_state/sync_state.ts
Normal file
36
app/assets/javascripts/ui_models/app_state/sync_state.ts
Normal 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' }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user