Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add migration pref pane #825

Merged
merged 12 commits into from
Feb 1, 2022
64 changes: 22 additions & 42 deletions app/assets/javascripts/components/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { ComponentView } from '@/components/ComponentView';
import { SmartTagsSection } from '@/components/Tags/SmartTagsSection';
import { TagsSection } from '@/components/Tags/TagsSection';
import { WebApplication } from '@/ui_models/application';
import { PANEL_NAME_NAVIGATION } from '@/views/constants';
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs';
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'preact/hooks';
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
import { PremiumModalProvider } from './Premium';
import {
PanelSide,
Expand All @@ -28,7 +21,6 @@ type Props = {
export const Navigation: FunctionComponent<Props> = observer(
({ application }) => {
const appState = useMemo(() => application.getAppState(), [application]);
const componentViewer = appState.foldersComponentViewer;
const enableNativeSmartTagsFeature =
appState.features.enableNativeSmartTagsFeature;
const [ref, setRef] = useState<HTMLDivElement | null>();
Expand Down Expand Up @@ -72,42 +64,30 @@ export const Navigation: FunctionComponent<Props> = observer(
data-aria-label="Navigation"
ref={setRef}
>
{componentViewer ? (
<div className="component-view-container">
<div className="component-view">
<ComponentView
componentViewer={componentViewer}
application={application}
appState={appState}
/>
</div>
</div>
) : (
<div id="navigation-content" className="content">
<div className="section-title-bar">
<div className="section-title-bar-header">
<div className="sk-h3 title">
<span className="sk-bold">Views</span>
</div>
{!enableNativeSmartTagsFeature && (
<div
className="sk-button sk-secondary-contrast wide"
onClick={onCreateNewTag}
title="Create a new tag"
>
<div className="sk-label">
<i className="icon ion-plus add-button" />
</div>
</div>
)}
<div id="navigation-content" className="content">
<div className="section-title-bar">
<div className="section-title-bar-header">
<div className="sk-h3 title">
<span className="sk-bold">Views</span>
</div>
</div>
<div className="scrollable">
<SmartTagsSection appState={appState} />
<TagsSection appState={appState} />
{!enableNativeSmartTagsFeature && (
<div
className="sk-button sk-secondary-contrast wide"
onClick={onCreateNewTag}
title="Create a new tag"
>
<div className="sk-label">
<i className="icon ion-plus add-button" />
</div>
</div>
)}
</div>
</div>
)}
<div className="scrollable">
<SmartTagsSection appState={appState} />
<TagsSection appState={appState} />
</div>
</div>
{ref && (
<PanelResizer
collapsable={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import {
ComponentArea,
ContentType,
FeatureIdentifier,
SNComponent,
SNTheme,
} from '@standardnotes/snjs';
Expand Down Expand Up @@ -107,10 +108,11 @@ export const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
const reloadToggleableComponents = useCallback(() => {
const toggleableComponents = (
application.getDisplayableItems(ContentType.Component) as SNComponent[]
).filter((component) =>
[ComponentArea.EditorStack, ComponentArea.TagsList].includes(
component.area
)
).filter(
(component) =>
[ComponentArea.EditorStack, ComponentArea.TagsList].includes(
component.area
) && component.identifier !== FeatureIdentifier.FoldersComponent
);
setToggleableComponents(toggleableComponents);
}, [application]);
Expand Down
9 changes: 3 additions & 6 deletions app/assets/javascripts/components/Tags/RootTagDropZone.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Icon } from '@/components/Icon';
import { usePremiumModal } from '@/components/Premium';
import {
FeaturesState,
TAG_FOLDERS_FEATURE_NAME,
} from '@/ui_models/app_state/features_state';
import { FeaturesState } 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';
Expand All @@ -22,8 +19,8 @@ export const RootTagDropZone: React.FC<Props> = observer(
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
() => ({
accept: ItemTypes.TAG,
canDrop: () => {
return true;
canDrop: (item) => {
return tagsState.hasParent(item.uuid);
},
drop: (item) => {
tagsState.assignParent(item.uuid, undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const TagsSectionTitle: FunctionComponent<Props> = observer(
className="ml-1 sk-bold color-grey-2 cursor-pointer"
onClick={showPremiumAlert}
>
Folders
&middot; Folders
</label>
</Tooltip>
</div>
Expand Down
41 changes: 23 additions & 18 deletions app/assets/javascripts/components/menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,27 @@ type ListElementProps = {
};

export const MenuItemListElement: FunctionComponent<ListElementProps> =
forwardRef(({ children, isFirstMenuItem }: ListElementProps, ref: Ref<HTMLLIElement>) => {
const child = children as VNode<unknown>;
forwardRef(
(
{ children, isFirstMenuItem }: ListElementProps,
ref: Ref<HTMLLIElement>
) => {
const child = children as VNode<unknown>;

return (
<li className="list-style-none" role="none" ref={ref}>
{{
...child,
props: {
...(child.props ? { ...child.props } : {}),
...(child.type === MenuItem
? {
tabIndex: isFirstMenuItem ? 0 : -1,
}
: {}),
},
}}
</li>
);
});
return (
<li className="list-style-none" role="none" ref={ref}>
{{
...child,
props: {
...(child.props ? { ...child.props } : {}),
...(child.type === MenuItem
? {
tabIndex: isFirstMenuItem ? 0 : -1,
}
: {}),
},
}}
</li>
);
}
);
4 changes: 3 additions & 1 deletion app/assets/javascripts/preferences/components/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const MenuItem: FunctionComponent<Props> = ({
onClick,
}) => (
<div
className={`preferences-menu-item select-none ${selected ? 'selected' : ''}`}
className={`preferences-menu-item select-none ${
selected ? 'selected' : ''
}`}
onClick={(e) => {
e.preventDefault();
onClick();
Expand Down
16 changes: 9 additions & 7 deletions app/assets/javascripts/preferences/panes/General.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,26 @@ 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;
application: WebApplication;
extensionsLatestVersions: ExtensionsLatestVersions,
extensionsLatestVersions: ExtensionsLatestVersions;
}

export const General: FunctionComponent<GeneralProps> = observer(
({
appState,
application,
extensionsLatestVersions
}) => (
({ appState, application, extensionsLatestVersions }) => (
<PreferencesPane>
<Tools application={application} />
<Defaults application={application} />
<ErrorReporting appState={appState} />
<Advanced application={application} appState={appState} extensionsLatestVersions={extensionsLatestVersions} />
<Migrations application={application} appState={appState} />
<Advanced
application={application}
appState={appState}
extensionsLatestVersions={extensionsLatestVersions}
/>
</PreferencesPane>
)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Button } from '@/components/Button';
import { Icon } from '@/components/Icon';
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { FunctionComponent } from 'preact';
import { useCallback, useState } from 'preact/hooks';
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '../../components';

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

export const CheckIcon: React.FC = () => {
return <Icon className="success min-w-4 min-h-4" type="check-bold" />;
};

const Migration3dot0dot0: FunctionComponent<Props> = ({ application }) => {
const [loading, setLoading] = useState(false);
const [complete, setComplete] = useState(false);
const [error, setError] = useState<unknown | null>(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 (
<>
<Subtitle>(3.0.0) Folders Component to Native Folders</Subtitle>
<Text>
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.
</Text>
<div className="flex flex-row items-center mt-3">
<Button
type="normal"
onClick={trigger}
className="m-2"
disabled={loading}
label="Run Now"
/>
{complete && (
<div className="ml-3">
<Text>Migration successful.</Text>
</div>
)}
{error && (
<div className="ml-3">
<Text>Something wrong happened. Please contact support.</Text>
</div>
)}
</div>
</>
);
};

export const Migrations: FunctionComponent<Props> = ({
application,
appState,
}) => {
const hasNativeFoldersEnabled = appState.features.enableNativeFoldersFeature;

if (!hasNativeFoldersEnabled) {
return null;
}

const hasFoldersFeature = appState.features.hasFolders;

if (!hasFoldersFeature) {
return null;
}

return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Migrations</Title>
<div className="h-2 w-full" />
<Migration3dot0dot0 application={application} appState={appState} />
</PreferencesSegment>
</PreferencesGroup>
);
};
Loading