feat: new note status indicator (#1679)

This commit is contained in:
Aman Harwara
2022-09-29 21:36:08 +05:30
committed by GitHub
parent 492c9bacbf
commit e0ec53f85a
3 changed files with 107 additions and 59 deletions

View File

@@ -0,0 +1,67 @@
import { ElementIds } from '@/Constants/ElementIDs'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { useState } from 'react'
import Icon from '../Icon/Icon'
export type NoteStatus = {
type: 'saving' | 'saved' | 'error'
message: string
desc?: string
}
type Props = {
status: NoteStatus | undefined
syncTakingTooLong: boolean
}
const NoteStatusIndicator = ({ status, syncTakingTooLong }: Props) => {
const [shouldShowTooltip, setShouldShowTooltip] = useState(false)
if (!status) {
return null
}
return (
<div className="relative">
<button
className={classNames(
'peer flex h-5 w-5 items-center justify-center rounded-full',
status.type === 'saving' && 'bg-contrast',
status.type === 'saved' && 'bg-success text-success-contrast',
status.type === 'error' && 'bg-danger text-danger-contrast',
syncTakingTooLong && 'bg-warning text-warning-contrast',
)}
onClick={() => setShouldShowTooltip((show) => !show)}
onBlur={() => setShouldShowTooltip(false)}
aria-describedby={ElementIds.NoteStatusTooltip}
>
<Icon
className={status.type === 'saving' ? 'animate-spin' : ''}
type={status.type === 'saved' ? 'check' : status.type === 'error' ? 'warning' : 'sync'}
size="small"
/>
<span className="sr-only">Note sync status</span>
</button>
<div
id={ElementIds.NoteStatusTooltip}
className={classNames(
shouldShowTooltip ? '' : 'hidden',
'absolute top-full right-0 min-w-max translate-x-2 translate-y-1 select-none rounded border border-border bg-default py-1.5 px-3 text-left peer-hover:block peer-focus:block',
)}
>
<div
className={classNames(
'text-sm font-bold',
status.type === 'error' && 'text-danger',
syncTakingTooLong && 'text-warning',
)}
>
{status.message}
</div>
{status.desc && <div className="mt-0.5">{status.desc}</div>}
</div>
</div>
)
}
export default NoteStatusIndicator

View File

@@ -41,6 +41,7 @@ import AutoresizingNoteViewTextarea from './AutoresizingTextarea'
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
import NoteTagsPanel from '../NoteTags/NoteTagsPanel'
import NoteTagsContainer from '../NoteTags/NoteTagsContainer'
import NoteStatusIndicator, { NoteStatus } from './NoteStatusIndicator'
import { PrefDefaults } from '@/Constants/PrefDefaults'
const MinimumStatusDuration = 400
@@ -48,11 +49,6 @@ const TextareaDebounce = 100
const NoteEditingDisabledText = 'Note editing disabled.'
const StickyHeaderScrollThresholdInPx = 20
type NoteStatus = {
message?: string
desc?: string
}
function sortAlphabetically(array: SNComponent[]): SNComponent[] {
return array.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1))
}
@@ -346,6 +342,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
break
case ApplicationEvent.LocalDatabaseWriteError:
this.showErrorStatus({
type: 'error',
message: 'Offline Saving Issue',
desc: 'Changes not saved',
})
@@ -503,7 +500,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
}
showSavingStatus() {
this.setStatus({ message: 'Saving…' }, false)
this.setStatus({ type: 'saving', message: 'Saving…' }, false)
}
showAllChangesSavedStatus() {
@@ -512,6 +509,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
syncTakingTooLong: false,
})
this.setStatus({
type: 'saved',
message: 'All changes saved' + (this.application.noAccount() ? ' offline' : ''),
})
}
@@ -519,6 +517,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
showErrorStatus(error?: NoteStatus) {
if (!error) {
error = {
type: 'error',
message: 'Sync Unreachable',
desc: 'Changes saved offline',
}
@@ -948,7 +947,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
: '',
)}
>
<div className="mb-2 flex flex-wrap items-start justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-0 xl:items-center">
<div className="mb-2 flex flex-wrap items-start justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-4 xl:items-center">
<div className={classNames(this.state.noteLocked && 'locked', 'flex flex-grow items-center')}>
<MobileItemsListButton />
<div className="title flex-grow overflow-auto">
@@ -966,60 +965,41 @@ class NoteView extends PureComponent<NoteViewProps, State> {
autoComplete="off"
/>
</div>
<NoteStatusIndicator status={this.state.noteStatus} syncTakingTooLong={this.state.syncTakingTooLong} />
</div>
{!this.state.shouldStickyHeader && (
<div className="flex flex-row-reverse items-center gap-3 md:flex-col-reverse md:items-end xl:flex-row xl:flex-nowrap xl:items-center">
{this.state.noteStatus?.message?.length && (
<div id="save-status-container" className={'xl:mr-5 xl:max-w-[16ch]'}>
<div id="save-status">
<div
className={
(this.state.syncTakingTooLong ? 'font-bold text-warning ' : '') +
(this.state.saveError ? 'font-bold text-danger ' : '') +
'message text-xs'
}
>
{this.state.noteStatus?.message}
</div>
{this.state.noteStatus?.desc && (
<div className="desc text-xs">{this.state.noteStatus.desc}</div>
)}
</div>
</div>
)}
<div className="flex items-center gap-3">
<NoteTagsPanel
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
noteTagsController={this.viewControllerManager.noteTagsController}
/>
<AttachedFilesButton
application={this.application}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
featuresController={this.viewControllerManager.featuresController}
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
filesController={this.viewControllerManager.filesController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
selectionController={this.viewControllerManager.selectionController}
/>
<ChangeEditorButton
application={this.application}
viewControllerManager={this.viewControllerManager}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<PinNoteButton
notesController={this.viewControllerManager.notesController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<NotesOptionsPanel
application={this.application}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
</div>
<div className="flex items-center gap-3">
<NoteTagsPanel
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
noteTagsController={this.viewControllerManager.noteTagsController}
/>
<AttachedFilesButton
application={this.application}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
featuresController={this.viewControllerManager.featuresController}
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
filesController={this.viewControllerManager.filesController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
selectionController={this.viewControllerManager.selectionController}
/>
<ChangeEditorButton
application={this.application}
viewControllerManager={this.viewControllerManager}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<PinNoteButton
notesController={this.viewControllerManager.notesController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<NotesOptionsPanel
application={this.application}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
</div>
)}
</div>

View File

@@ -9,4 +9,5 @@ export const ElementIds = {
NoteTextEditor: 'note-text-editor',
NoteTitleEditor: 'note-title-editor',
RootId: 'app-group-root',
NoteStatusTooltip: 'note-status-tooltip',
} as const