Skip to content

Commit

Permalink
feat: tags folder premium (#774)
Browse files Browse the repository at this point in the history
* feat: upgrade premium in tags section

refactor: move TagsSection to react

feat: show premium on Tag section

feat: keep drag and drop features always active

fix: drag & drop tweak with premium

feat: premium messages

fix: remove fill in svg icons

fix: change tag list color (temporary)

style: remove dead code

refactor: clarify names and modules

fix: draggable behind feature toggle

feat: add button in TagSection & design

* feat: fix features loading with app state (#775)

* fix: distinguish between app launch and start

* fix: update state for footer too

* fix: wait for application launch event

Co-authored-by: Laurent Senta <[email protected]>

* feat: tags folder with folder text design (#776)

* feat: add folder text

* fix: sn stylekit colors

* fix: root drop zone

* chore: upgrade stylekit

* fix: hide dropzone when feature is disabled

* chore: bump versions now that they are released

Co-authored-by: Mo <[email protected]>
  • Loading branch information
laurentsenta and moughxyz committed Dec 21, 2021
1 parent caaa2d0 commit 784bc72
Show file tree
Hide file tree
Showing 24 changed files with 367 additions and 230 deletions.
3 changes: 3 additions & 0 deletions app/assets/icons/ic-add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions app/assets/icons/ic-folder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion app/assets/icons/ic-list-bulleted.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion app/assets/icons/ic-menu-arrow-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion app/assets/javascripts/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import { QuickSettingsMenuDirective } from './components/QuickSettingsMenu/Quick
import { ComponentViewDirective } from '@/components/ComponentView';
import { TagsListDirective } from '@/components/TagsList';
import { PinNoteButtonDirective } from '@/components/PinNoteButton';
import { TagsSectionDirective } from './components/Tags/TagsSection';

function reloadHiddenFirefoxTab(): boolean {
/**
Expand Down Expand Up @@ -190,7 +191,8 @@ const startApplication: StartApplication = async function startApplication(
.directive('notesListOptionsMenu', NotesListOptionsDirective)
.directive('icon', IconDirective)
.directive('noteTagsContainer', NoteTagsContainerDirective)
.directive('tags', TagsListDirective)
.directive('tagsList', TagsListDirective)
.directive('tagsSection', TagsSectionDirective)
.directive('preferences', PreferencesDirective)
.directive('purchaseFlow', PurchaseFlowDirective)
.directive('pinNoteButton', PinNoteButtonDirective);
Expand Down
2 changes: 2 additions & 0 deletions app/assets/javascripts/components/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import MarkdownIcon from '../../icons/ic-markdown.svg';
import CodeIcon from '../../icons/ic-code.svg';

import AccessibilityIcon from '../../icons/ic-accessibility.svg';
import AddIcon from '../../icons/ic-add.svg';
import HelpIcon from '../../icons/ic-help.svg';
import KeyboardIcon from '../../icons/ic-keyboard.svg';
import ListBulleted from '../../icons/ic-list-bulleted.svg';
Expand Down Expand Up @@ -100,6 +101,7 @@ const ICONS = {
more: MoreIcon,
tune: TuneIcon,
accessibility: AccessibilityIcon,
add: AddIcon,
help: HelpIcon,
keyboard: KeyboardIcon,
'list-bulleted': ListBulleted,
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/components/Premium/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { usePremiumModal, PremiumModalProvider } from './usePremiumModal';
49 changes: 49 additions & 0 deletions app/assets/javascripts/components/Premium/usePremiumModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { FunctionalComponent } from 'preact';
import { useCallback, useContext, useState } from 'preact/hooks';
import { createContext } from 'react';
import { PremiumFeaturesModal } from '../PremiumFeaturesModal';

type PremiumModalContextData = {
activate: (featureName: string) => void;
};

const PremiumModalContext = createContext<PremiumModalContextData | null>(null);

const PremiumModalProvider_ = PremiumModalContext.Provider;

export const usePremiumModal = (): PremiumModalContextData => {
const value = useContext(PremiumModalContext);

if (!value) {
throw new Error('invalid PremiumModal context');
}

return value;
};

export const PremiumModalProvider: FunctionalComponent = ({ children }) => {
const [featureName, setFeatureName] = useState<null | string>(null);

const activate = setFeatureName;

const closeModal = useCallback(() => {
setFeatureName(null);
}, [setFeatureName]);

const showModal = !!featureName;

return (
<>
{showModal && (
<PremiumFeaturesModal
showModal={!!featureName}
featureName={featureName}
onClose={closeModal}
/>
)}
<PremiumModalProvider_ value={{ activate }}>
{children}
</PremiumModalProvider_>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -306,16 +306,6 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
onClose={closeQuickSettingsMenu}
isEnabled={focusModeEnabled}
/>
{appState.features.hasUnfinishedFoldersFeature && (
<TagNestingSwitch
application={application}
onToggle={(checked: boolean) => {
appState.features.hasFolders = checked;
}}
isEnabled={appState.features.hasFolders}
onClose={closeQuickSettingsMenu}
/>
)}
<div className="h-1px my-2 bg-border"></div>
<button
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
Expand Down

This file was deleted.

59 changes: 59 additions & 0 deletions app/assets/javascripts/components/RootTagDropZone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
FeaturesState,
TAG_FOLDERS_FEATURE_NAME,
} from '@/ui_models/app_state/features_state';
import { TagsState } from '@/ui_models/app_state/tags_state';
import { observer } from 'mobx-react-lite';
import { useDrop } from 'react-dnd';
import { usePremiumModal } from './Premium';
import { DropItem, DropProps, ItemTypes } from './TagsListItem';

type Props = {
tagsState: TagsState;
featuresState: FeaturesState;
};

export const RootTagDropZone: React.FC<Props> = observer(
({ tagsState, featuresState }) => {
const premiumModal = usePremiumModal();
const isNativeFoldersEnabled = featuresState.enableNativeFoldersFeature;
const hasFolders = tagsState.hasFolders;

const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
() => ({
accept: ItemTypes.TAG,
canDrop: () => {
return true;
},
drop: (item) => {
if (!hasFolders) {
premiumModal.activate(TAG_FOLDERS_FEATURE_NAME);
return;
}

tagsState.assignParent(item.uuid, undefined);
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}),
[tagsState, hasFolders, premiumModal]
);

if (!isNativeFoldersEnabled || !hasFolders) {
return null;
}

return (
<div
ref={dropRef}
className={`root-drop ${canDrop ? 'active' : ''} ${
isOver ? 'is-over' : ''
}`}
>
Move the tag here to remove it from its folder.
</div>
);
}
);
107 changes: 107 additions & 0 deletions app/assets/javascripts/components/Tags/TagsSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { TagsList } from '@/components/TagsList';
import { toDirective } from '@/components/utils';
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import {
FeaturesState,
TAG_FOLDERS_FEATURE_NAME,
TAG_FOLDERS_FEATURE_TOOLTIP,
} from '@/ui_models/app_state/features_state';
import { Tooltip } from '@reach/tooltip';
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { useCallback } from 'preact/hooks';
import { IconButton } from '../IconButton';
import { PremiumModalProvider, usePremiumModal } from '../Premium';

type Props = {
application: WebApplication;
appState: AppState;
};

const TagAddButton: FunctionComponent<{
appState: AppState;
features: FeaturesState;
}> = observer(({ appState, features }) => {
const isNativeFoldersEnabled = features.enableNativeFoldersFeature;

if (!isNativeFoldersEnabled) {
return null;
}

return (
<IconButton
icon="add"
title="Create a new tag"
onClick={() => appState.createNewTag()}
/>
);
});

const TagTitle: FunctionComponent<{
features: FeaturesState;
}> = observer(({ features }) => {
const isNativeFoldersEnabled = features.enableNativeFoldersFeature;
const hasFolders = features.hasFolders;
const modal = usePremiumModal();

const showPremiumAlert = useCallback(() => {
modal.activate(TAG_FOLDERS_FEATURE_NAME);
}, [modal]);

if (!isNativeFoldersEnabled) {
return (
<>
<div className="sk-h3 title">
<span className="sk-bold">Tags</span>
</div>
</>
);
}

if (hasFolders) {
return (
<>
<div className="sk-h3 title">
<span className="sk-bold">Folders</span>
</div>
</>
);
}

return (
<>
<div className="sk-h3 title">
<span className="sk-bold">Tags</span>
<Tooltip label={TAG_FOLDERS_FEATURE_TOOLTIP}>
<label
className="ml-1 sk-bold color-grey-2 cursor-pointer"
onClick={showPremiumAlert}
>
Folders
</label>
</Tooltip>
</div>
</>
);
});

export const TagsSection: FunctionComponent<Props> = observer(
({ application, appState }) => {
return (
<PremiumModalProvider>
<section>
<div className="tags-title-section section-title-bar">
<div className="section-title-bar-header">
<TagTitle features={appState.features} />
<TagAddButton appState={appState} features={appState.features} />
</div>
</div>
<TagsList application={application} appState={appState} />
</section>
</PremiumModalProvider>
);
}
);

export const TagsSectionDirective = toDirective<Props>(TagsSection);
13 changes: 9 additions & 4 deletions app/assets/javascripts/components/TagsList.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PremiumModalProvider } from '@/components/Premium';
import { confirmDialog } from '@/services/alertService';
import { STRING_DELETE_TAG } from '@/strings';
import { WebApplication } from '@/ui_models/application';
Expand All @@ -11,7 +12,8 @@ import { useCallback } from 'preact/hooks';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { TouchBackend } from 'react-dnd-touch-backend';
import { RootTagDropZone, TagsListItem } from './TagsListItem';
import { RootTagDropZone } from './RootTagDropZone';
import { TagsListItem } from './TagsListItem';
import { toDirective } from './utils';

type Props = {
Expand Down Expand Up @@ -114,7 +116,7 @@ export const TagsList: FunctionComponent<Props> = observer(
const backend = isMobile({ tablet: true }) ? TouchBackend : HTML5Backend;

return (
<>
<PremiumModalProvider>
<DndProvider backend={backend}>
{allTags.length === 0 ? (
<div className="no-tags-placeholder">
Expand All @@ -136,11 +138,14 @@ export const TagsList: FunctionComponent<Props> = observer(
/>
);
})}
<RootTagDropZone tagsState={appState.tags} />
<RootTagDropZone
tagsState={appState.tags}
featuresState={appState.features}
/>
</>
)}
</DndProvider>
</>
</PremiumModalProvider>
);
}
);
Expand Down
Loading

0 comments on commit 784bc72

Please sign in to comment.