- {!tag.errorDecrypting ? (
-
-
#
-
-
{noteCounts.get()}
-
- ) : null}
- {tag.conflictOf && (
-
- Conflicted Copy {tag.conflictOf}
-
- )}
- {tag.errorDecrypting && !tag.waitingForKey && (
-
Missing Keys
- )}
- {tag.errorDecrypting && tag.waitingForKey && (
-
Waiting For Keys
- )}
- {isSelected && (
-
- {!isEditing && (
-
- Rename
-
+ <>
+
+ {!tag.errorDecrypting ? (
+
+ {hasFolders && isNativeFoldersEnabled && hasAtLeastOneFolder && (
+
+
+
+ )}
+
+
+
+
+
{noteCounts.get()}
+
+ ) : null}
+
+ {tag.conflictOf && (
+
+ Conflicted Copy {tag.conflictOf}
+
+ )}
+ {tag.errorDecrypting && !tag.waitingForKey && (
+
Missing Keys
+ )}
+ {tag.errorDecrypting && tag.waitingForKey && (
+
Waiting For Keys
)}
- {isEditing && (
-
- Save
-
+ {isSelected && (
+
)}
-
- Delete
-
+
+ {showChildren && (
+ <>
+ {childrenTags.map((tag) => {
+ return (
+
+ );
+ })}
+ >
)}
-
+ >
);
}
);
diff --git a/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx b/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx
index e843af206a7..16cc8d9c082 100644
--- a/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx
+++ b/app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx
@@ -34,7 +34,7 @@ export const PurchaseFlowView: FunctionComponent
=
const { currentPane } = appState.purchaseFlow;
return (
-
+
diff --git a/app/assets/javascripts/typings/hoist-non-react-statics.d.ts b/app/assets/javascripts/typings/hoist-non-react-statics.d.ts
new file mode 100644
index 00000000000..4a0f7856f2d
--- /dev/null
+++ b/app/assets/javascripts/typings/hoist-non-react-statics.d.ts
@@ -0,0 +1,67 @@
+/* eslint-disable @typescript-eslint/ban-types */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+// Type definitions for hoist-non-react-statics 3.3
+// Project: https://github.com/mridgway/hoist-non-react-statics#readme
+// Definitions by: JounQin
, James Reggio
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+// TypeScript Version: 2.8
+// https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/hoist-non-react-statics
+
+declare module 'hoist-non-react-statics' {
+ interface REACT_STATICS {
+ childContextTypes: true;
+ contextType: true;
+ contextTypes: true;
+ defaultProps: true;
+ displayName: true;
+ getDefaultProps: true;
+ getDerivedStateFromError: true;
+ getDerivedStateFromProps: true;
+ mixins: true;
+ propTypes: true;
+ type: true;
+ }
+
+ interface KNOWN_STATICS {
+ name: true;
+ length: true;
+ prototype: true;
+ caller: true;
+ callee: true;
+ arguments: true;
+ arity: true;
+ }
+
+ interface MEMO_STATICS {
+ $$typeof: true;
+ compare: true;
+ defaultProps: true;
+ displayName: true;
+ propTypes: true;
+ type: true;
+ }
+
+ interface FORWARD_REF_STATICS {
+ $$typeof: true;
+ render: true;
+ defaultProps: true;
+ displayName: true;
+ propTypes: true;
+ }
+
+ export type NonReactStatics<
+ S extends React.ComponentType,
+ C extends {
+ [key: string]: true;
+ } = {}
+ > = {
+ [key in Exclude<
+ keyof S,
+ S extends React.MemoExoticComponent
+ ? keyof MEMO_STATICS | keyof C
+ : S extends React.ForwardRefExoticComponent
+ ? keyof FORWARD_REF_STATICS | keyof C
+ : keyof REACT_STATICS | keyof KNOWN_STATICS | keyof C
+ >]: S[key];
+ };
+}
diff --git a/app/assets/javascripts/ui_models/app_state/app_state.ts b/app/assets/javascripts/ui_models/app_state/app_state.ts
index 98e88e81b46..24d1f5f25af 100644
--- a/app/assets/javascripts/ui_models/app_state/app_state.ts
+++ b/app/assets/javascripts/ui_models/app_state/app_state.ts
@@ -22,6 +22,7 @@ import {
runInAction,
} from 'mobx';
import { ActionsMenuState } from './actions_menu_state';
+import { FeaturesState } from './features_state';
import { NotesState } from './notes_state';
import { NotesViewState } from './notes_view_state';
import { NoteTagsState } from './note_tags_state';
@@ -57,8 +58,8 @@ export enum EventSource {
type ObserverCallback = (event: AppStateEvent, data?: any) => Promise;
export class AppState {
- readonly enableUnfinishedFeatures: boolean = (window as any)
- ?._enable_unfinished_features;
+ readonly enableUnfinishedFeatures: boolean =
+ window?._enable_unfinished_features;
$rootScope: ng.IRootScopeService;
$timeout: ng.ITimeoutService;
@@ -86,6 +87,7 @@ export class AppState {
readonly sync = new SyncState();
readonly searchOptions: SearchOptionsState;
readonly notes: NotesState;
+ readonly features: FeaturesState;
readonly tags: TagsState;
readonly notesView: NotesViewState;
isSessionsModalVisible = false;
@@ -115,7 +117,12 @@ export class AppState {
this,
this.appEventObserverRemovers
);
- this.tags = new TagsState(application, this.appEventObserverRemovers);
+ this.features = new FeaturesState(application);
+ this.tags = new TagsState(
+ application,
+ this.appEventObserverRemovers,
+ this.features
+ );
this.noAccountWarning = new NoAccountWarningState(
application,
this.appEventObserverRemovers
@@ -187,6 +194,7 @@ export class AppState {
this.unsubApp = undefined;
this.observers.length = 0;
this.appEventObserverRemovers.forEach((remover) => remover());
+ this.features.deinit();
this.appEventObserverRemovers.length = 0;
if (this.rootScopeCleanup1) {
this.rootScopeCleanup1();
@@ -430,6 +438,10 @@ export class AppState {
}
public async createNewTag() {
+ if (this.templateTag) {
+ return;
+ }
+
const newTag = (await this.application.createTemplateItem(
ContentType.Tag
)) as SNTag;
diff --git a/app/assets/javascripts/ui_models/app_state/features_state.ts b/app/assets/javascripts/ui_models/app_state/features_state.ts
new file mode 100644
index 00000000000..ca022d8b4d0
--- /dev/null
+++ b/app/assets/javascripts/ui_models/app_state/features_state.ts
@@ -0,0 +1,87 @@
+import {
+ ApplicationEvent,
+ FeatureIdentifier,
+ FeatureStatus,
+} from '@standardnotes/snjs';
+import { computed, makeObservable, observable, runInAction } from 'mobx';
+import { WebApplication } from '../application';
+
+export const TAG_FOLDERS_FEATURE_NAME = 'Tag folders';
+export const TAG_FOLDERS_FEATURE_TOOLTIP =
+ 'A Plus or Pro plan is required to enable Tag folders.';
+
+/**
+ * Holds state for premium/non premium features for the current user features,
+ * and eventually for in-development features (feature flags).
+ */
+export class FeaturesState {
+ readonly enableUnfinishedFeatures: boolean =
+ window?._enable_unfinished_features;
+
+ _hasFolders = false;
+ private unsub: () => void;
+
+ constructor(private application: WebApplication) {
+ this._hasFolders = this.hasNativeFolders();
+
+ makeObservable(this, {
+ _hasFolders: observable,
+ hasFolders: computed,
+ enableNativeFoldersFeature: computed,
+ });
+
+ this.unsub = this.application.addEventObserver(async (eventName) => {
+ switch (eventName) {
+ case ApplicationEvent.FeaturesUpdated:
+ case ApplicationEvent.Launched:
+ runInAction(() => {
+ this._hasFolders = this.hasNativeFolders();
+ });
+ break;
+ default:
+ break;
+ }
+ });
+ }
+
+ public deinit() {
+ this.unsub();
+ }
+
+ public get enableNativeFoldersFeature(): boolean {
+ return this.enableUnfinishedFeatures;
+ }
+
+ public get hasFolders(): boolean {
+ return this._hasFolders;
+ }
+
+ public set hasFolders(hasFolders: boolean) {
+ if (!hasFolders) {
+ this._hasFolders = false;
+ return;
+ }
+
+ if (!this.hasNativeFolders()) {
+ this.application.alertService?.alert(
+ `${TAG_FOLDERS_FEATURE_NAME} requires at least a Plus Subscription.`
+ );
+ this._hasFolders = false;
+ return;
+ }
+
+ this._hasFolders = hasFolders;
+ }
+
+ private hasNativeFolders(): boolean {
+ if (!this.enableNativeFoldersFeature) {
+ return false;
+ }
+
+ const status = this.application.getFeatureStatus(
+ FeatureIdentifier.TagNesting
+ );
+
+ return status === FeatureStatus.Entitled;
+ }
+}
diff --git a/app/assets/javascripts/ui_models/app_state/note_tags_state.ts b/app/assets/javascripts/ui_models/app_state/note_tags_state.ts
index 3ad0900e8e9..b49699c507c 100644
--- a/app/assets/javascripts/ui_models/app_state/note_tags_state.ts
+++ b/app/assets/javascripts/ui_models/app_state/note_tags_state.ts
@@ -176,16 +176,9 @@ export class NoteTagsState {
async addTagToActiveNote(tag: SNTag): Promise {
const { activeNote } = this;
+
if (activeNote) {
- const parentChainTags = this.application.getTagParentChain(tag);
- const tagsToAdd = [...parentChainTags, tag];
- await Promise.all(
- tagsToAdd.map(async (tag) => {
- await this.application.changeItem(tag.uuid, (mutator) => {
- mutator.addItemAsRelationship(activeNote);
- });
- })
- );
+ await this.application.addTagHierarchyToNote(activeNote, tag);
this.application.sync();
this.reloadTags();
}
diff --git a/app/assets/javascripts/ui_models/app_state/tags_state.ts b/app/assets/javascripts/ui_models/app_state/tags_state.ts
index 3aeb99d100b..ef63453a6c7 100644
--- a/app/assets/javascripts/ui_models/app_state/tags_state.ts
+++ b/app/assets/javascripts/ui_models/app_state/tags_state.ts
@@ -1,13 +1,15 @@
-import { ContentType, SNSmartTag, SNTag } from '@standardnotes/snjs';
+import { ContentType, SNSmartTag, SNTag, UuidString } from '@standardnotes/snjs';
import {
action,
computed,
makeAutoObservable,
makeObservable,
observable,
- runInAction,
+ runInAction
} from 'mobx';
import { WebApplication } from '../application';
+import { FeaturesState } from './features_state';
+
export class TagsState {
tags: SNTag[] = [];
@@ -16,14 +18,20 @@ export class TagsState {
constructor(
private application: WebApplication,
- appEventListeners: (() => void)[]
+ appEventListeners: (() => void)[],
+ private features: FeaturesState
) {
this.tagsCountsState = new TagsCountsState(this.application);
makeObservable(this, {
- tags: observable,
- smartTags: observable,
+ tags: observable.ref,
+ smartTags: observable.ref,
+ hasFolders: computed,
+ hasAtLeastOneFolder: computed,
+
+ assignParent: action,
+ rootTags: computed,
tagsCount: computed,
});
@@ -48,9 +56,68 @@ export class TagsState {
return this.tagsCountsState.counts[tag.uuid] || 0;
}
+ getChildren(tag: SNTag): SNTag[] {
+ if (!this.hasFolders) {
+ return [];
+ }
+
+ if (this.application.isTemplateItem(tag)) {
+ return [];
+ }
+
+ const children = this.application.getTagChildren(tag);
+ const childrenUuids = children.map((childTag) => childTag.uuid);
+ const childrenTags = this.tags.filter((tag) =>
+ childrenUuids.includes(tag.uuid)
+ );
+ return childrenTags;
+ }
+
+ isValidTagParent(parentUuid: UuidString, tagUuid: UuidString): boolean {
+ return this.application.isValidTagParent(parentUuid, tagUuid);
+ }
+
+ public async assignParent(
+ tagUuid: string,
+ parentUuid: string | undefined
+ ): Promise {
+ const tag = this.application.findItem(tagUuid) as SNTag;
+
+ const parent =
+ parentUuid && (this.application.findItem(parentUuid) as SNTag);
+
+ if (!parent) {
+ await this.application.unsetTagParent(tag);
+ } else {
+ await this.application.setTagParent(parent, tag);
+ }
+
+ await this.application.sync();
+ }
+
+ get rootTags(): SNTag[] {
+ if (!this.hasFolders) {
+ return this.tags;
+ }
+
+ return this.tags.filter((tag) => !this.application.getTagParent(tag));
+ }
+
get tagsCount(): number {
return this.tags.length;
}
+
+ public get hasFolders(): boolean {
+ return this.features.hasFolders;
+ }
+
+ public set hasFolders(hasFolders: boolean) {
+ this.features.hasFolders = hasFolders;
+ }
+
+ public get hasAtLeastOneFolder(): boolean {
+ return this.tags.some((tag) => !!this.application.getTagParent(tag));
+ }
}
/**
diff --git a/app/assets/javascripts/ui_models/editor.ts b/app/assets/javascripts/ui_models/editor.ts
index 606d239c647..787c1769cc8 100644
--- a/app/assets/javascripts/ui_models/editor.ts
+++ b/app/assets/javascripts/ui_models/editor.ts
@@ -1,14 +1,21 @@
-import { SNNote, ContentType, PayloadSource, UuidString, TagMutator } from '@standardnotes/snjs';
+import {
+ SNNote,
+ ContentType,
+ PayloadSource,
+ UuidString,
+ TagMutator,
+ SNTag,
+} from '@standardnotes/snjs';
import { WebApplication } from './application';
+import { NoteTagsState } from './app_state/note_tags_state';
export class Editor {
-
- public note!: SNNote
- private application: WebApplication
- private _onNoteChange?: () => void
- private _onNoteValueChange?: (note: SNNote, source?: PayloadSource) => void
- private removeStreamObserver?: () => void
- public isTemplateNote = false
+ public note!: SNNote;
+ private application: WebApplication;
+ private _onNoteChange?: () => void;
+ private _onNoteValueChange?: (note: SNNote, source?: PayloadSource) => void;
+ private removeStreamObserver?: () => void;
+ public isTemplateNote = false;
constructor(
application: WebApplication,
@@ -66,22 +73,15 @@ export class Editor {
* Reverts the editor to a blank state, removing any existing note from view,
* and creating a placeholder note.
*/
- async reset(
- noteTitle = '',
- noteTag?: UuidString,
- ) {
- const note = await this.application.createTemplateItem(
- ContentType.Note,
- {
- text: '',
- title: noteTitle,
- references: []
- }
- ) as SNNote;
+ async reset(noteTitle = '', noteTag?: UuidString) {
+ const note = (await this.application.createTemplateItem(ContentType.Note, {
+ text: '',
+ title: noteTitle,
+ references: [],
+ })) as SNNote;
if (noteTag) {
- await this.application.changeItem(noteTag, (m) => {
- m.addItemAsRelationship(note);
- });
+ const tag = this.application.findItem(noteTag) as SNTag;
+ await this.application.addTagHierarchyToNote(note, tag);
}
if (!this.isTemplateNote || this.note.title !== note.title) {
this.setNote(note as SNNote, true);
@@ -106,7 +106,9 @@ export class Editor {
* Register to be notified when the editor's note's values change
* (and thus a new object reference is created)
*/
- public onNoteValueChange(callback: (note: SNNote, source?: PayloadSource) => void) {
+ public onNoteValueChange(
+ callback: (note: SNNote, source?: PayloadSource) => void
+ ) {
this._onNoteValueChange = callback;
}
diff --git a/app/assets/javascripts/utils.ts b/app/assets/javascripts/utils/index.ts
similarity index 99%
rename from app/assets/javascripts/utils.ts
rename to app/assets/javascripts/utils/index.ts
index 01a43bc3e77..7a271451665 100644
--- a/app/assets/javascripts/utils.ts
+++ b/app/assets/javascripts/utils/index.ts
@@ -1,6 +1,7 @@
import { Platform, platformFromString } from '@standardnotes/snjs';
import { IsDesktopPlatform, IsWebPlatform } from '@/version';
import { EMAIL_REGEX } from '@Views/constants';
+export { isMobile } from './isMobile';
declare const process: {
env: {
diff --git a/app/assets/javascripts/utils/isMobile.ts b/app/assets/javascripts/utils/isMobile.ts
new file mode 100644
index 00000000000..5167dc0a302
--- /dev/null
+++ b/app/assets/javascripts/utils/isMobile.ts
@@ -0,0 +1,28 @@
+/**
+ * source: https://github.com/juliangruber/is-mobile
+ *
+ * (MIT)
+ * Copyright (c) 2013 Julian Gruber
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+const mobileRE =
+ /(android|bb\d+|meego).+mobile|armv7l|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series[46]0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i;
+
+const tabletRE = /android|ipad|playbook|silk/i;
+
+export type Opts = {
+ tablet?: boolean;
+};
+
+export const isMobile = (opts: Opts = {}) => {
+ const ua = navigator.userAgent || navigator.vendor;
+
+ if (typeof ua !== 'string') {
+ return false;
+ }
+
+ return mobileRE.test(ua) || (!!opts.tablet && tabletRE.test(ua));
+};
diff --git a/app/assets/javascripts/views/application/application-view.pug b/app/assets/javascripts/views/application/application-view.pug
index 66d15f53b33..e39519b6c42 100644
--- a/app/assets/javascripts/views/application/application-view.pug
+++ b/app/assets/javascripts/views/application/application-view.pug
@@ -3,7 +3,7 @@
)
#app.app(
ng-class='self.state.appClass',
- ng-if='!self.state.needsUnlock && self.state.ready'
+ ng-if='!self.state.needsUnlock && self.state.launched'
)
tags-view(application='self.application')
notes-view(
@@ -13,7 +13,7 @@
editor-group-view.flex-grow(application='self.application')
footer-view(
- ng-if='!self.state.needsUnlock && self.state.ready'
+ ng-if='!self.state.needsUnlock && self.state.launched'
application='self.application'
)
diff --git a/app/assets/javascripts/views/application/application_view.ts b/app/assets/javascripts/views/application/application_view.ts
index 104e668288f..fa8681ac1d7 100644
--- a/app/assets/javascripts/views/application/application_view.ts
+++ b/app/assets/javascripts/views/application/application_view.ts
@@ -3,25 +3,29 @@ import { WebDirective } from '@/types';
import { getPlatformString } from '@/utils';
import template from './application-view.pug';
import { AppStateEvent, PanelResizedData } from '@/ui_models/app_state';
-import { ApplicationEvent, Challenge, removeFromArray } from '@standardnotes/snjs';
import {
- PANEL_NAME_NOTES,
- PANEL_NAME_TAGS
-} from '@/views/constants';
-import {
- STRING_DEFAULT_FILE_ERROR
-} from '@/strings';
+ ApplicationEvent,
+ Challenge,
+ removeFromArray,
+} from '@standardnotes/snjs';
+import { PANEL_NAME_NOTES, PANEL_NAME_TAGS } from '@/views/constants';
+import { STRING_DEFAULT_FILE_ERROR } from '@/strings';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { alertDialog } from '@/services/alertService';
-class ApplicationViewCtrl extends PureViewCtrl {
+class ApplicationViewCtrl extends PureViewCtrl<
+ unknown,
+ {
+ started?: boolean;
+ launched?: boolean;
+ needsUnlock?: boolean;
+ appClass: string;
+ }
+> {
public platformString: string;
private notesCollapsed = false;
private tagsCollapsed = false;
+
/**
* To prevent stale state reads (setState is async),
* challenges is a mutable array
@@ -76,7 +80,7 @@ class ApplicationViewCtrl extends PureViewCtrl {
this.challenges.push(challenge);
});
- }
+ },
});
await this.application.launch();
}
@@ -90,14 +94,17 @@ class ApplicationViewCtrl extends PureViewCtrl .tag-fold {
+ width: 22px;
+ display: flex;
+ align-items: center;
+ height: 100%;
+ }
+
> .tag-icon {
- width: 10px;
- opacity: 0.2;
- font-size: var(--sn-stylekit-font-size-h2);
- font-weight: bold;
- margin-right: 6px;
+ display: flex;
+ align-items: center;
+ height: 100%;
+
+ &.draggable {
+ cursor: move;
+ }
+
+ &.propose-folders {
+ cursor: help;
+ }
}
> .title {
@extend .focus\:outline-none;
@extend .focus\:shadow-none;
+ font-size: 14px;
+ line-height: 18px;
+
width: 80%;
background-color: transparent;
font-weight: 600;
@@ -84,6 +137,7 @@
cursor: pointer;
text-overflow: ellipsis;
width: 75%;
+ flex-grow: 1;
// Required for Safari to avoid highlighting when dragging panel resizers
// Make sure to undo if it's selected (for editing)
@@ -118,21 +172,29 @@
}
}
- > .menu {
- font-size: 11px;
+ .meta {
+ padding-left: 3px;
- > .item {
- margin-right: 2px;
+ &.with-folders {
+ padding-left: 25px;
}
- opacity: 0.5;
- font-weight: bold;
- clear: both;
- margin-top: 2px;
- margin-bottom: 2px;
+ > .menu {
+ font-size: 11px;
- &:hover {
- opacity: 1;
+ > .item {
+ margin-right: 2px;
+ }
+
+ opacity: 0.5;
+ font-weight: bold;
+ clear: both;
+ margin-top: 2px;
+ margin-bottom: 2px;
+
+ &:hover {
+ opacity: 1;
+ }
}
}
@@ -145,7 +207,8 @@
}
&:hover:not(.selected),
- &.selected {
+ &.selected,
+ &.is-drag-over {
background-color: var(--sn-stylekit-secondary-contrast-background-color);
color: var(--sn-stylekit-secondary-contrast-foreground-color);
diff --git a/package.json b/package.json
index 915f144f41b..02bb911f0dd 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
"pug-loader": "^2.4.0",
"sass-loader": "^12.2.0",
"serve-static": "^1.14.1",
- "sn-stylekit": "5.2.17",
+ "sn-stylekit": "5.2.20",
"svg-jest": "^1.0.1",
"ts-jest": "^27.0.7",
"ts-loader": "^9.2.6",
@@ -85,12 +85,16 @@
"@reach/dialog": "^0.16.2",
"@reach/listbox": "^0.16.2",
"@standardnotes/features": "1.10.2",
+ "@reach/tooltip": "^0.16.2",
"@standardnotes/sncrypto-web": "1.5.3",
"@standardnotes/snjs": "2.25.0",
"mobx": "^6.3.5",
"mobx-react-lite": "^3.2.2",
"preact": "^10.5.15",
- "qrcode.react": "^1.0.1"
+ "qrcode.react": "^1.0.1",
+ "react-dnd": "^14.0.4",
+ "react-dnd-html5-backend": "^14.0.2",
+ "react-dnd-touch-backend": "^14.1.1"
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": "eslint --cache --fix",
diff --git a/yarn.lock b/yarn.lock
index 799fd1ddca0..4914c62c689 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1872,6 +1872,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.9.2":
+ version "7.16.3"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
+ integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.10.4", "@babel/template@^7.12.7":
version "7.12.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
@@ -2523,6 +2530,20 @@
tiny-warning "^1.0.3"
tslib "^2.3.0"
+"@reach/tooltip@^0.16.2":
+ version "0.16.2"
+ resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.16.2.tgz#8448cee341476e4f795fa7192f7a0864f06b8085"
+ integrity sha512-wtJPnbJ6l4pmudMpQHGU9v1NS4ncDgcwRNi9re9KsIdsM525zccZvHQLteBKYiaW4ib7k09t2dbwhyNU9oa0Iw==
+ dependencies:
+ "@reach/auto-id" "0.16.0"
+ "@reach/portal" "0.16.2"
+ "@reach/rect" "0.16.0"
+ "@reach/utils" "0.16.0"
+ "@reach/visually-hidden" "0.16.0"
+ prop-types "^15.7.2"
+ tiny-warning "^1.0.3"
+ tslib "^2.3.0"
+
"@reach/utils@0.15.2":
version "0.15.2"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.15.2.tgz#a63a761eb36266bf33d65cb91520dd85a04ae116"
@@ -2547,6 +2568,21 @@
prop-types "^15.7.2"
tslib "^2.3.0"
+"@react-dnd/asap@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.0.tgz#b300eeed83e9801f51bd66b0337c9a6f04548651"
+ integrity sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ==
+
+"@react-dnd/invariant@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-2.0.0.tgz#09d2e81cd39e0e767d7da62df9325860f24e517e"
+ integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==
+
+"@react-dnd/shallowequal@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a"
+ integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==
+
"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
@@ -4641,6 +4677,15 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
+dnd-core@14.0.1:
+ version "14.0.1"
+ resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-14.0.1.tgz#76d000e41c494983210fb20a48b835f81a203c2e"
+ integrity sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A==
+ dependencies:
+ "@react-dnd/asap" "^4.0.0"
+ "@react-dnd/invariant" "^2.0.0"
+ redux "^4.1.1"
+
dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@@ -5748,6 +5793,13 @@ he@1.2.x, he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
@@ -8482,6 +8534,32 @@ react-clientside-effect@^1.2.5:
dependencies:
"@babel/runtime" "^7.12.13"
+react-dnd-html5-backend@^14.0.2:
+ version "14.0.2"
+ resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-14.0.2.tgz#25019388f6abdeeda3a6fea835dff155abb2085c"
+ integrity sha512-QgN6rYrOm4UUj6tIvN8ovImu6uP48xBXF2rzVsp6tvj6d5XQ7OjHI4SJ/ZgGobOneRAU3WCX4f8DGCYx0tuhlw==
+ dependencies:
+ dnd-core "14.0.1"
+
+react-dnd-touch-backend@^14.1.1:
+ version "14.1.1"
+ resolved "https://registry.yarnpkg.com/react-dnd-touch-backend/-/react-dnd-touch-backend-14.1.1.tgz#d8875ef1cf8dcbf1741a4e03dd5b147c4fbda5e4"
+ integrity sha512-ITmfzn3fJrkUBiVLO6aJZcnu7T8C+GfwZitFryGsXKn5wYcUv+oQBeh9FYcMychmVbDdeUCfvEtTk9O+DKmAaw==
+ dependencies:
+ "@react-dnd/invariant" "^2.0.0"
+ dnd-core "14.0.1"
+
+react-dnd@^14.0.4:
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-14.0.4.tgz#ffb4ea0e2a3a5532f9c6294d565742008a52b8b0"
+ integrity sha512-AFJJXzUIWp5WAhgvI85ESkDCawM0lhoVvfo/lrseLXwFdH3kEO3v8I2C81QPqBW2UEyJBIPStOhPMGYGFtq/bg==
+ dependencies:
+ "@react-dnd/invariant" "^2.0.0"
+ "@react-dnd/shallowequal" "^2.0.0"
+ dnd-core "14.0.1"
+ fast-deep-equal "^3.1.3"
+ hoist-non-react-statics "^3.3.2"
+
react-focus-lock@^2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.2.tgz#f1e4db5e25cd8789351f2bd5ebe91e9dcb9c2922"
@@ -8494,7 +8572,7 @@ react-focus-lock@^2.5.2:
use-callback-ref "^1.2.5"
use-sidecar "^1.0.5"
-react-is@^16.8.1:
+react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -8595,6 +8673,13 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+redux@^4.1.1:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
+ integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
regenerate-unicode-properties@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@@ -9086,10 +9171,10 @@ slice-ansi@^5.0.0:
ansi-styles "^6.0.0"
is-fullwidth-code-point "^4.0.0"
-sn-stylekit@5.2.17:
- version "5.2.17"
- resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-5.2.17.tgz#5d8ceefacc044d24f71f99c343bc4e0a9f867e3b"
- integrity sha512-YjIstWUjkRYc0zWKxTcLDk8ZKfb9nskQAbsNsihKbJsko1tQbDywrvoQff8TmsE8kXedTd7z/8yUYrYlqgKp6g==
+sn-stylekit@5.2.20:
+ version "5.2.20"
+ resolved "https://registry.yarnpkg.com/sn-stylekit/-/sn-stylekit-5.2.20.tgz#c18f40ff3aaf4c59af89152439a8efbdde35f2dd"
+ integrity sha512-JymHBiZOzQPfCqHYgnVPSA2PwJqiKR268qqQoEMqI85MMAWSG3WYzuKEbd0LgfIQAKLElCxJjeZkrhejyRg+2A==
dependencies:
"@reach/listbox" "^0.15.0"
"@reach/menu-button" "^0.15.1"