diff --git a/.eslintrc b/.eslintrc index 74fa349e3d5..3841621ad15 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,7 +19,10 @@ "camelcase": "warn", "sort-imports": "off", "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks - "react-hooks/exhaustive-deps": "error" // Checks effect dependencies + "react-hooks/exhaustive-deps": "error", // Checks effect dependencies + "eol-last": "error", + "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], + "no-trailing-spaces": "error" }, "env": { "browser": true diff --git a/.gitignore b/.gitignore index 4656f67cc2b..01c40b0e4ae 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # OS & IDE .DS_Store +.idea # Ignore bundler config. /.bundle diff --git a/app/assets/javascripts/components/Button.tsx b/app/assets/javascripts/components/Button.tsx index 4a383edda04..bf13b5509a9 100644 --- a/app/assets/javascripts/components/Button.tsx +++ b/app/assets/javascripts/components/Button.tsx @@ -1,6 +1,6 @@ import { FunctionComponent } from 'preact'; -const baseClass = `rounded px-4 py-1.75 font-bold text-sm fit-content cursor-pointer`; +const baseClass = `rounded px-4 py-1.75 font-bold text-sm fit-content`; const normalClass = `${baseClass} bg-default color-text border-solid border-gray-300 border-1 \ focus:bg-contrast hover:bg-contrast`; @@ -12,15 +12,19 @@ export const Button: FunctionComponent<{ type: 'normal' | 'primary'; label: string; onClick: () => void; -}> = ({ type, label, className = '', onClick }) => { + disabled?: boolean; +}> = ({ type, label, className = '', onClick, disabled = false }) => { const buttonClass = type === 'primary' ? primaryClass : normalClass; + const cursorClass = disabled ? 'cursor-default' : 'cursor-pointer'; + return ( diff --git a/app/assets/javascripts/preferences/PreferencesView.tsx b/app/assets/javascripts/preferences/PreferencesView.tsx index e1472fcbc14..239955ce52a 100644 --- a/app/assets/javascripts/preferences/PreferencesView.tsx +++ b/app/assets/javascripts/preferences/PreferencesView.tsx @@ -1,19 +1,21 @@ import { RoundIconButton } from '@/components/RoundIconButton'; import { TitleBar, Title } from '@/components/TitleBar'; import { FunctionComponent } from 'preact'; -import { HelpAndFeedback, Security } from './panes'; +import { AccountPreferences, HelpAndFeedback, Security } from './panes'; import { observer } from 'mobx-react-lite'; import { PreferencesMenu } from './preferences-menu'; import { PreferencesMenuView } from './PreferencesMenuView'; +import { WebApplication } from '@/ui_models/application'; const PaneSelector: FunctionComponent<{ prefs: PreferencesMenu; -}> = observer(({ prefs: menu }) => { + application: WebApplication; +}> = observer(({ prefs: menu, application }) => { switch (menu.selectedPaneId) { case 'general': return null; case 'account': - return null; + return ; case 'appearance': return null; case 'security': @@ -33,15 +35,19 @@ const PaneSelector: FunctionComponent<{ const PreferencesCanvas: FunctionComponent<{ preferences: PreferencesMenu; -}> = observer(({ preferences: prefs }) => ( + application: WebApplication; +}> = observer(({ preferences: prefs, application }) => (
- +
)); -const PreferencesView: FunctionComponent<{ close: () => void }> = observer( - ({ close }) => { +const PreferencesView: FunctionComponent<{ + close: () => void; + application: WebApplication; +}> = observer( + ({ close, application }) => { const prefs = new PreferencesMenu(); return ( @@ -58,7 +64,7 @@ const PreferencesView: FunctionComponent<{ close: () => void }> = observer( icon="close" /> - + ); } @@ -66,12 +72,16 @@ const PreferencesView: FunctionComponent<{ close: () => void }> = observer( export interface PreferencesWrapperProps { appState: { preferences: { isOpen: boolean; closePreferences: () => void } }; + application: WebApplication; } export const PreferencesViewWrapper: FunctionComponent = - observer(({ appState }) => { + observer(({ appState, application }) => { if (!appState.preferences.isOpen) return null; return ( - appState.preferences.closePreferences()} /> + appState.preferences.closePreferences()} + /> ); }); diff --git a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx new file mode 100644 index 00000000000..6b034d7c6d3 --- /dev/null +++ b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx @@ -0,0 +1,12 @@ +import { Sync } from '@/preferences/panes/account'; +import { PreferencesPane } from '@/preferences/components'; +import { observer } from 'mobx-react-lite'; +import { WebApplication } from '@/ui_models/application'; + +export const AccountPreferences = observer(({application}: {application: WebApplication}) => { + return ( + + + + ); +}); diff --git a/app/assets/javascripts/preferences/panes/account/Sync.tsx b/app/assets/javascripts/preferences/panes/account/Sync.tsx new file mode 100644 index 00000000000..7190bfa0e69 --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/Sync.tsx @@ -0,0 +1,60 @@ +import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/preferences/components'; +import { Button } from '@/components/Button'; +import { SyncQueueStrategy } from '@node_modules/@standardnotes/snjs'; +import { STRING_GENERIC_SYNC_ERROR } from '@/strings'; +import { useState } from '@node_modules/preact/hooks'; +import { dateToLocalizedString } from '@/utils'; +import { observer } from '@node_modules/mobx-react-lite'; +import { WebApplication } from '@/ui_models/application'; + +type Props = { + application: WebApplication; +}; + +const Sync = observer(({ application }: Props) => { + const formatLastSyncDate = (lastUpdatedDate: Date) => { + return dateToLocalizedString(lastUpdatedDate); + }; + + const [isSyncingInProgress, setIsSyncingInProgress] = useState(false); + const [lastSyncDate, setLastSyncDate] = useState(formatLastSyncDate(application.getLastSyncDate() as Date)); + + const doSynchronization = async () => { + setIsSyncingInProgress(true); + + const response = await application.sync({ + queueStrategy: SyncQueueStrategy.ForceSpawnNew, + checkIntegrity: true + }); + setIsSyncingInProgress(false); + if (response && response.error) { + application.alertService!.alert(STRING_GENERIC_SYNC_ERROR); + } else { + setLastSyncDate(formatLastSyncDate(application.getLastSyncDate() as Date)); + } + }; + + return ( + + +
+
+ Sync + + Last synced on {lastSyncDate} + +
+
+
+
+ ); +}); + +export default Sync; diff --git a/app/assets/javascripts/preferences/panes/account/index.ts b/app/assets/javascripts/preferences/panes/account/index.ts new file mode 100644 index 00000000000..2225b588171 --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/index.ts @@ -0,0 +1 @@ +export { default as Sync } from './Sync'; diff --git a/app/assets/javascripts/preferences/panes/index.ts b/app/assets/javascripts/preferences/panes/index.ts index f176e8116c0..741508ad065 100644 --- a/app/assets/javascripts/preferences/panes/index.ts +++ b/app/assets/javascripts/preferences/panes/index.ts @@ -1,2 +1,3 @@ export * from './HelpFeedback'; export * from './Security'; +export * from './AccountPreferences'; diff --git a/app/assets/javascripts/views/application/application-view.pug b/app/assets/javascripts/views/application/application-view.pug index cff5275ee42..9bd688c897d 100644 --- a/app/assets/javascripts/views/application/application-view.pug +++ b/app/assets/javascripts/views/application/application-view.pug @@ -28,6 +28,7 @@ ) preferences( app-state='self.appState' + application='self.application' ) challenge-modal( ng-repeat="challenge in self.challenges track by challenge.id" diff --git a/app/assets/stylesheets/_ui.scss b/app/assets/stylesheets/_ui.scss index e1f7a1dee56..6fa1b0b9af8 100644 --- a/app/assets/stylesheets/_ui.scss +++ b/app/assets/stylesheets/_ui.scss @@ -199,6 +199,10 @@ $screen-md-max: ($screen-lg-min - 1) !default; } } +.cursor-default { + cursor: default; +} + .fill-current { fill: currentColor; } diff --git a/yarn.lock b/yarn.lock index e54b8deb922..30920454c8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2016,6 +2016,11 @@ resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.1.1.tgz#834701c2e14d31eb204bff90457fa05e9183464a" integrity sha512-E9zDYZ1gJkVZBEzd7a1L2haQ4GYeH1lUrY87UmDH1AMYUHW+c0SqZ71af1fBNqGzrx3EZSXk+Qzr7RyOa6N1Mw== +"@standardnotes/features@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.0.0.tgz#906af029b6e58241689ca37436982c37a888a418" + integrity sha512-PEQyP/p/TQLVcNYcbu9jEIWNRqBrFFG1Qyy8QIcvNUt5o4lpLZGEY1T+PJUsPSisnuKKNpQrgVLc9LjhUKpuYw== + "@standardnotes/sncrypto-common@^1.2.7", "@standardnotes/sncrypto-common@^1.2.9": version "1.2.9" resolved "https://registry.yarnpkg.com/@standardnotes/sncrypto-common/-/sncrypto-common-1.2.9.tgz#5212a959e4ec563584e42480bfd39ef129c3cbdf" @@ -2029,12 +2034,13 @@ "@standardnotes/sncrypto-common" "^1.2.7" libsodium-wrappers "^0.7.8" -"@standardnotes/snjs@2.7.21": - version "2.7.21" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.7.21.tgz#db451e5facaf5fa41fa509eb1f304723929c3541" - integrity sha512-GhkGk1LJmD494COZkSOgyHaUnGnLWNLlSuCZMTwbw3dgkN5PjobbRhfDvEZaLqjwok+h9nkiQt3hugQ3h6Cy5w== +"@standardnotes/snjs@2.7.23": + version "2.7.23" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.7.23.tgz#fedc9c025301dbe20ed2d598fb378e36f90ff64e" + integrity sha512-eoEwKlV2PZcJXFbCt6bgovu9nldVoT7DPoterTBo/NZ4odRILOwxLA1SAgL5H5FYPb9NHkwaaCt9uTdIqdNYhA== dependencies: "@standardnotes/auth" "3.1.1" + "@standardnotes/features" "1.0.0" "@standardnotes/sncrypto-common" "^1.2.9" "@svgr/babel-plugin-add-jsx-attribute@^5.4.0":