From 70f4dd63532276bba41eaa1ec9d2552a74de8722 Mon Sep 17 00:00:00 2001 From: Mo Date: Tue, 1 Feb 2022 12:31:37 -0600 Subject: [PATCH] feat: enable folders by default and remove from experimental features --- .../javascripts/components/Navigation.tsx | 17 --- .../javascripts/components/NotesView.tsx | 2 +- .../components/PremiumFeaturesModal.tsx | 4 +- .../components/Tags/RootTagDropZone.tsx | 71 ++++++------ .../components/Tags/SmartTagsListItem.tsx | 17 ++- .../javascripts/components/Tags/TagsList.tsx | 2 +- .../components/Tags/TagsListItem.tsx | 14 +-- .../components/Tags/TagsSection.tsx | 44 +++++++- .../components/Tags/TagsSectionAddButton.tsx | 8 +- .../components/Tags/TagsSectionTitle.tsx | 29 +++-- .../javascripts/preferences/panes/General.tsx | 2 - .../panes/general-segments/Migrations.tsx | 103 ------------------ .../ui_models/app_state/features_state.ts | 18 --- .../ui_models/app_state/tags_state.ts | 45 ++------ app/assets/stylesheets/_navigation.scss | 10 +- app/assets/stylesheets/_notes.scss | 6 +- 16 files changed, 127 insertions(+), 265 deletions(-) delete mode 100644 app/assets/javascripts/preferences/panes/general-segments/Migrations.tsx diff --git a/app/assets/javascripts/components/Navigation.tsx b/app/assets/javascripts/components/Navigation.tsx index 3d9eb0691..c97c790cc 100644 --- a/app/assets/javascripts/components/Navigation.tsx +++ b/app/assets/javascripts/components/Navigation.tsx @@ -21,8 +21,6 @@ type Props = { export const Navigation: FunctionComponent = observer( ({ application }) => { const appState = useMemo(() => application.getAppState(), [application]); - const enableNativeSmartTagsFeature = - appState.features.enableNativeSmartTagsFeature; const [ref, setRef] = useState(); const [panelWidth, setPanelWidth] = useState(0); @@ -39,10 +37,6 @@ export const Navigation: FunctionComponent = observer( }; }, [application]); - const onCreateNewTag = useCallback(() => { - appState.tags.createNewTemplate(); - }, [appState]); - const panelResizeFinishCallback: ResizeFinishCallback = useCallback( (width, _lastLeft, _isMaxWidth, isCollapsed) => { application.setPreference(PrefKey.TagsPanelWidth, width); @@ -70,17 +64,6 @@ export const Navigation: FunctionComponent = observer(
Views
- {!enableNativeSmartTagsFeature && ( -
-
- -
-
- )}
diff --git a/app/assets/javascripts/components/NotesView.tsx b/app/assets/javascripts/components/NotesView.tsx index 6969ab49c..927e56809 100644 --- a/app/assets/javascripts/components/NotesView.tsx +++ b/app/assets/javascripts/components/NotesView.tsx @@ -164,7 +164,7 @@ export const NotesView: FunctionComponent = observer( >
-
+
{panelTitle}
- Enable premium features + Enable Premium Features
@@ -65,7 +65,7 @@ export const PremiumFeaturesModal: FunctionalComponent = ({ className="w-full rounded no-border py-2 font-bold bg-info color-info-contrast hover:brightness-130 focus:brightness-130 cursor-pointer" ref={plansButtonRef} > - See our plans + See Plans
diff --git a/app/assets/javascripts/components/Tags/RootTagDropZone.tsx b/app/assets/javascripts/components/Tags/RootTagDropZone.tsx index 1eb7ab919..35fc6e5a4 100644 --- a/app/assets/javascripts/components/Tags/RootTagDropZone.tsx +++ b/app/assets/javascripts/components/Tags/RootTagDropZone.tsx @@ -11,45 +11,38 @@ type Props = { featuresState: FeaturesState; }; -export const RootTagDropZone: React.FC = observer( - ({ tagsState, featuresState }) => { - const premiumModal = usePremiumModal(); - const isNativeFoldersEnabled = featuresState.enableNativeFoldersFeature; +export const RootTagDropZone: React.FC = observer(({ tagsState }) => { + const premiumModal = usePremiumModal(); - const [{ isOver, canDrop }, dropRef] = useDrop( - () => ({ - accept: ItemTypes.TAG, - canDrop: (item) => { - return tagsState.hasParent(item.uuid); - }, - drop: (item) => { - tagsState.assignParent(item.uuid, undefined); - }, - collect: (monitor) => ({ - isOver: !!monitor.isOver(), - canDrop: !!monitor.canDrop(), - }), + const [{ isOver, canDrop }, dropRef] = useDrop( + () => ({ + accept: ItemTypes.TAG, + canDrop: (item) => { + return tagsState.hasParent(item.uuid); + }, + drop: (item) => { + tagsState.assignParent(item.uuid, undefined); + }, + collect: (monitor) => ({ + isOver: !!monitor.isOver(), + canDrop: !!monitor.canDrop(), }), - [tagsState, premiumModal] - ); + }), + [tagsState, premiumModal] + ); - if (!isNativeFoldersEnabled) { - return null; - } - - return ( -
- -

- Move the tag here to
- remove it from its folder. -

-
- ); - } -); + return ( +
+ +

+ Move the tag here to
+ remove it from its folder. +

+
+ ); +}); diff --git a/app/assets/javascripts/components/Tags/SmartTagsListItem.tsx b/app/assets/javascripts/components/Tags/SmartTagsListItem.tsx index b8270a872..a0a7e4a55 100644 --- a/app/assets/javascripts/components/Tags/SmartTagsListItem.tsx +++ b/app/assets/javascripts/components/Tags/SmartTagsListItem.tsx @@ -37,7 +37,6 @@ export const SmartTagsListItem: FunctionComponent = observer( const level = 0; const isSelected = tagsState.selected === tag; const isEditing = tagsState.editingTag === tag; - const isSmartTagsEnabled = features.enableNativeSmartTagsFeature; useEffect(() => { setTitle(tag.title || ''); @@ -88,7 +87,7 @@ export const SmartTagsListItem: FunctionComponent = observer( tagsState.remove(tag); }, [tagsState, tag]); - const isFaded = !isSmartTagsEnabled && !tag.isAllTag; + const isFaded = !tag.isAllTag; const iconType = smartTagIconType(tag); return ( @@ -104,14 +103,12 @@ export const SmartTagsListItem: FunctionComponent = observer( > {!tag.errorDecrypting ? (
- {isSmartTagsEnabled && ( -
- -
- )} +
+ +
= observer(({ appState }) => { {allTags.length === 0 ? (
- No tags. Create one using the add button above. + No tags or folders. Create one using the add button above.
) : ( <> diff --git a/app/assets/javascripts/components/Tags/TagsListItem.tsx b/app/assets/javascripts/components/Tags/TagsListItem.tsx index 0aacdd462..b71922a92 100644 --- a/app/assets/javascripts/components/Tags/TagsListItem.tsx +++ b/app/assets/javascripts/components/Tags/TagsListItem.tsx @@ -37,7 +37,6 @@ export const TagsListItem: FunctionComponent = observer( const hasChildren = childrenTags.length > 0; const hasFolders = features.hasFolders; - const isNativeFoldersEnabled = features.enableNativeFoldersFeature; const hasAtLeastOneFolder = tagsState.hasAtLeastOneFolder; const premiumModal = usePremiumModal(); @@ -114,13 +113,13 @@ export const TagsListItem: FunctionComponent = observer( type: ItemTypes.TAG, item: { uuid: tag.uuid }, canDrag: () => { - return isNativeFoldersEnabled; + return true; }, collect: (monitor) => ({ isDragging: !!monitor.isDragging(), }), }), - [tag, isNativeFoldersEnabled] + [tag] ); const [{ isOver, canDrop }, dropRef] = useDrop( @@ -160,7 +159,7 @@ export const TagsListItem: FunctionComponent = observer( > {!tag.errorDecrypting ? (
- {isNativeFoldersEnabled && hasAtLeastOneFolder && ( + {hasAtLeastOneFolder && (
= observer( />
)} -
+
= observer( ({ appState }) => { + const [hasMigration, setHasMigration] = useState(false); + + const checkIfMigrationNeeded = useCallback(() => { + setHasMigration(appState.application.hasTagsNeedingFoldersMigration()); + }, [appState.application]); + + useEffect(() => { + appState.application.addEventObserver(async (event) => { + const events = [ + ApplicationEvent.CompletedInitialSync, + ApplicationEvent.SignedIn, + ]; + if (events.includes(event)) { + checkIfMigrationNeeded(); + } + }); + }, [appState.application, checkIfMigrationNeeded]); + + const runMigration = useCallback(async () => { + if ( + await appState.application.alertService.confirm( + 'Introducing native, built-in nested tags without requiring the legacy Folders component.

' + + " To get started, we'll need to migrate any tags containing a dot character to the new system.

" + + ' This migration will convert any tags with dots appearing in their name into a natural' + + ' hierarchy that is compatible with the new nested tags feature.' + + ' Running this migration will remove any "." characters appearing in tag names.', + 'New: Folders to Nested Tags', + 'Run Migration' + ) + ) { + appState.application.migrateTagsToFolders().then(() => { + checkIfMigrationNeeded(); + }); + } + }, [appState.application, checkIfMigrationNeeded]); + return (
- + = observer( - ({ tags, features }) => { - const isNativeFoldersEnabled = features.enableNativeFoldersFeature; - - if (!isNativeFoldersEnabled) { - return null; - } - + ({ tags }) => { return ( void; }; export const TagsSectionTitle: FunctionComponent = observer( - ({ features }) => { - const isNativeFoldersEnabled = features.enableNativeFoldersFeature; - const hasFolders = features.hasFolders; + ({ features, hasMigration, onClickMigration }) => { + const entitledToFolders = features.hasFolders; const modal = usePremiumModal(); const showPremiumAlert = useCallback(() => { modal.activate(TAG_FOLDERS_FEATURE_NAME); }, [modal]); - if (!isNativeFoldersEnabled) { - return ( - <> -
- Tags -
- - ); - } - - if (hasFolders) { + if (entitledToFolders) { return ( <>
Folders + {hasMigration && ( + + )}
); @@ -52,7 +51,7 @@ export const TagsSectionTitle: FunctionComponent = observer( className="ml-1 sk-bold color-grey-2 cursor-pointer" onClick={showPremiumAlert} > - · Folders + Folders
diff --git a/app/assets/javascripts/preferences/panes/General.tsx b/app/assets/javascripts/preferences/panes/General.tsx index aacc4a1db..c84ba5410 100644 --- a/app/assets/javascripts/preferences/panes/General.tsx +++ b/app/assets/javascripts/preferences/panes/General.tsx @@ -6,7 +6,6 @@ import { ErrorReporting, Tools, Defaults } from './general-segments'; import { ExtensionsLatestVersions } from '@/preferences/panes/extensions-segments'; import { Advanced } from '@/preferences/panes/account'; import { observer } from 'mobx-react-lite'; -import { Migrations } from './general-segments/Migrations'; interface GeneralProps { appState: AppState; @@ -20,7 +19,6 @@ export const General: FunctionComponent = observer( - { - return ; -}; - -const Migration3dot0dot0: FunctionComponent = ({ application }) => { - const [loading, setLoading] = useState(false); - const [complete, setComplete] = useState(false); - const [error, setError] = useState(null); - - const trigger = useCallback(() => { - setLoading(true); - setError(null); - setComplete(false); - application - .migrateTagDotsToHierarchy() - .then(() => { - setLoading(false); - setError(null); - setComplete(true); - }) - .catch((error: unknown) => { - setLoading(false); - setError(error); - setComplete(false); - }); - }, [application, setLoading, setError]); - - return ( - <> - (3.0.0) Folders Component to Native Folders - - This migration transform tags with "." in their title into a hierarchy - of parents. This lets your transform tag hierarchies created with the - folder component into native tag folders. - -
-
- - ); -}; - -export const Migrations: FunctionComponent = ({ - application, - appState, -}) => { - const hasNativeFoldersEnabled = appState.features.enableNativeFoldersFeature; - - if (!hasNativeFoldersEnabled) { - return null; - } - - const hasFoldersFeature = appState.features.hasFolders; - - if (!hasFoldersFeature) { - return null; - } - - return ( - - - Migrations -
- - - - ); -}; diff --git a/app/assets/javascripts/ui_models/app_state/features_state.ts b/app/assets/javascripts/ui_models/app_state/features_state.ts index 323f2931f..25ad9026f 100644 --- a/app/assets/javascripts/ui_models/app_state/features_state.ts +++ b/app/assets/javascripts/ui_models/app_state/features_state.ts @@ -42,8 +42,6 @@ export class FeaturesState { _hasFolders: observable, _hasSmartTags: observable, hasFolders: computed, - enableNativeFoldersFeature: computed, - enableNativeSmartTagsFeature: computed, _premiumAlertFeatureName: observable, showPremiumAlert: action, closePremiumAlert: action, @@ -71,14 +69,6 @@ export class FeaturesState { this.unsub(); } - public get enableNativeFoldersFeature(): boolean { - return this.enableUnfinishedFeatures; - } - - public get enableNativeSmartTagsFeature(): boolean { - return this.enableUnfinishedFeatures; - } - public get hasFolders(): boolean { return this._hasFolders; } @@ -97,10 +87,6 @@ export class FeaturesState { } private hasNativeFolders(): boolean { - if (!this.enableNativeFoldersFeature) { - return false; - } - const status = this.application.getFeatureStatus( FeatureIdentifier.TagNesting ); @@ -109,10 +95,6 @@ export class FeaturesState { } private hasNativeSmartTags(): boolean { - if (!this.enableNativeSmartTagsFeature) { - return false; - } - const status = this.application.getFeatureStatus( FeatureIdentifier.SmartFilters ); 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 e5c31efb1..4004c15e4 100644 --- a/app/assets/javascripts/ui_models/app_state/tags_state.ts +++ b/app/assets/javascripts/ui_models/app_state/tags_state.ts @@ -172,10 +172,6 @@ export class TagsState { } getChildren(tag: SNTag): SNTag[] { - if (!this.features.enableNativeFoldersFeature) { - return []; - } - if (this.application.isTemplateItem(tag)) { return []; } @@ -235,10 +231,6 @@ export class TagsState { } get rootTags(): SNTag[] { - if (!this.features.enableNativeFoldersFeature) { - return this.tags; - } - return this.tags.filter((tag) => !this.application.getTagParent(tag)); } @@ -355,35 +347,20 @@ export class TagsState { } if (isTemplateChange) { - if (this.features.enableNativeSmartTagsFeature) { - const isSmartTagTitle = this.application.isSmartTagTitle(newTitle); + const isSmartTagTitle = this.application.isSmartTagTitle(newTitle); - if (isSmartTagTitle) { - if (!this.features.hasSmartTags) { - await this.features.showPremiumAlert(SMART_TAGS_FEATURE_NAME); - return; - } + if (isSmartTagTitle) { + if (!this.features.hasSmartTags) { + await this.features.showPremiumAlert(SMART_TAGS_FEATURE_NAME); + return; } - - const insertedTag = await this.application.createTagOrSmartTag( - newTitle - ); - runInAction(() => { - this.selected = insertedTag as SNTag; - }); - } else { - // Legacy code, remove me after we enableNativeSmartTagsFeature for everyone. - // See https://app.asana.com/0/0/1201612665552831/f - const insertedTag = await this.application.insertItem(tag); - const changedTag = await this.application.changeItem( - insertedTag.uuid, - (m) => { - m.title = newTitle; - } - ); - this.selected = changedTag as SNTag; - await this.application.saveItem(insertedTag.uuid); } + + const insertedTag = await this.application.createTagOrSmartTag(newTitle); + this.application.sync(); + runInAction(() => { + this.selected = insertedTag as SNTag; + }); } else { await this.application.changeAndSaveItem( tag.uuid, diff --git a/app/assets/stylesheets/_navigation.scss b/app/assets/stylesheets/_navigation.scss index f5426dcbc..c672faa9e 100644 --- a/app/assets/stylesheets/_navigation.scss +++ b/app/assets/stylesheets/_navigation.scss @@ -5,6 +5,8 @@ height: 100%; } +$content-horizontal-padding: 16px; + #navigation { user-select: none; -moz-user-select: none; @@ -20,15 +22,15 @@ .section-title-bar { color: var(--sn-stylekit-secondary-foreground-color); - padding-top: 15px; + padding-top: 0.8125rem; padding-bottom: 8px; - padding-left: 14px; - padding-right: 14px; + padding-left: $content-horizontal-padding; + padding-right: $content-horizontal-padding; font-size: 12px; } .no-tags-placeholder { - padding: 0px 14px; + padding: 0px $content-horizontal-padding; font-size: 12px; opacity: 0.4; margin-top: -5px; diff --git a/app/assets/stylesheets/_notes.scss b/app/assets/stylesheets/_notes.scss index ca6b773b2..75e91ec2f 100644 --- a/app/assets/stylesheets/_notes.scss +++ b/app/assets/stylesheets/_notes.scss @@ -25,6 +25,10 @@ flex-direction: column; } + #notes-title-bar-container { + padding: 0.8125rem; + } + #notes-title-bar { font-weight: normal; overflow: visible; @@ -149,7 +153,7 @@ } .flag-icons { - padding: .135rem 0; + padding: 0.135rem 0; &, & > * {