diff --git a/src/cloud/components/Editor/index.tsx b/src/cloud/components/Editor/index.tsx index e38c280628..4d4ab3ff2a 100644 --- a/src/cloud/components/Editor/index.tsx +++ b/src/cloud/components/Editor/index.tsx @@ -40,8 +40,6 @@ import { mdiEyeOutline, mdiViewSplitVertical, mdiDotsHorizontal, - mdiCloudOffOutline, - mdiCloudSyncOutline, } from '@mdi/js' import EditorToolButton from './EditorToolButton' import { not } from 'ramda' @@ -101,6 +99,7 @@ import CustomizedMarkdownPreviewer from '../MarkdownView/CustomizedMarkdownPrevi import BottomBarButton from '../BottomBarButton' import { YText } from 'yjs/dist/src/internals' import { useLocalSnapshot } from '../../lib/stores/localSnapshots' +import SyncStatus from '../Topbar/SyncStatus' type LayoutMode = 'split' | 'preview' | 'editor' @@ -159,6 +158,8 @@ const Editor = ({ : 'preview' ) const [editorContent, setEditorContent] = useState('') + const [showingConnectIssueMessage, setShowingShowingConnectIssueMessage] = + useState(false) const docRef = useRef('') const router = useRouter() const { state } = router @@ -485,6 +486,16 @@ const Editor = ({ }) }, [openModal, onTemplatePickCallback]) + useEffect(() => { + if (connState == 'reconnecting') { + setShowingShowingConnectIssueMessage(true) + } else if (connState == 'disconnected') { + setShowingShowingConnectIssueMessage(true) + } else { + setShowingShowingConnectIssueMessage(false) + } + }, [connState]) + const toggleScrollSync = useCallback(() => { setScrollSync(not) }, []) @@ -846,57 +857,6 @@ const Editor = ({ { type: 'separator', }, - ...(connState === 'reconnecting' - ? [ - { - type: 'button', - iconPath: mdiCloudSyncOutline, - variant: 'danger', - disabled: true, - label: translate(lngKeys.EditorReconnectAttempt), - tooltip: ( - <> - {translate(lngKeys.EditorReconnectAttempt1)} -
- {translate(lngKeys.EditorReconnectAttempt2)} - - ), - }, - ] - : connState === 'disconnected' - ? [ - { - type: 'button', - iconPath: mdiCloudOffOutline, - variant: 'danger', - onClick: () => realtime.connect(), - label: translate(lngKeys.EditorReconnectDisconnected), - tooltip: ( - <> - {translate(lngKeys.EditorReconnectDisconnected1)} -
- {translate(lngKeys.EditorReconnectDisconnected2)} - - ), - }, - ] - : connState === 'loaded' - ? [ - { - type: 'button', - variant: 'secondary' as const, - disabled: true, - label: translate(lngKeys.EditorReconnectSyncing), - tooltip: ( - <> - {translate(lngKeys.EditorReconnectSyncing1)} -
- {translate(lngKeys.EditorReconnectSyncing2)} - - ), - }, - ] - : []), ...(docIsEditable && currentUserIsCoreMember ? [ { @@ -1069,6 +1029,9 @@ const Editor = ({ )} + {showingConnectIssueMessage && ( + + )} ) @@ -1102,6 +1065,7 @@ const StyledBottomBar = styled.div` height: 24px; background-color: ${({ theme }) => theme.colors.background.primary}; box-sizing: content-box; + & > :first-child { flex: 1; } @@ -1142,6 +1106,7 @@ const StyledShortcodeConvertMenu = styled.div` const StyledLayoutDimensions = styled.div` width: 100%; + &.preview, .preview { width: 100%; @@ -1163,17 +1128,21 @@ const ToolbarRow = styled.div` border: solid 1px ${({ theme }) => theme.colors.border.second}; border-radius: 5px; ` + interface FontOptionsProps { fontSize: string fontFamily: string } + const StyledEditorWrapper = styled.div` position: relative; height: auto; width: 50%; + &.layout-editor { width: 100%; } + &.layout-preview { display: none; } @@ -1189,19 +1158,23 @@ const StyledEditorWrapper = styled.div` const StyledPreview = styled.div` height: 100%; width: 50%; + &.layout-split { width: 50%; + .scroller { height: 100%; overflow: auto; border-left: 1px solid ${({ theme }) => theme.colors.border.main}; } } + &.layout-preview { padding-top: ${({ theme }) => theme.sizes.spaces.sm}px; margin: 0 auto; width: 100%; } + &.layout-editor { display: none; } @@ -1223,21 +1196,25 @@ const StyledEditor = styled.div` height: auto; min-height: 0; font-size: 15px; + &.preview, .preview { ${rightSidePageLayout}; margin: auto; } + & .CodeMirrorWrapper { height: 100%; word-break: break-word; } + & .CodeMirror { width: 100%; height: 100%; position: relative; z-index: 0 !important; line-height: 1.4em; + .CodeMirror-hints { position: absolute; z-index: 10; @@ -1254,6 +1231,7 @@ const StyledEditor = styled.div` font-family: monospace; list-style: none; } + .CodeMirror-hint { position: relative; margin: 0; @@ -1264,8 +1242,10 @@ const StyledEditor = styled.div` cursor: pointer; font-size: ${({ theme }) => theme.sizes.fonts.xsm}px; } + li.CodeMirror-hint-active { color: ${({ theme }) => theme.colors.variants.primary.base}; + &:before { content: ''; display: block; @@ -1277,15 +1257,18 @@ const StyledEditor = styled.div` background-color: ${({ theme }) => theme.colors.variants.primary.base}; } } + & .remote-caret { position: relative; border-left: 1px solid black; margin-left: -1px; box-sizing: border-box; + &:hover > div { opacity: 1; transition-delay: 0s; } + & > div { position: absolute; left: -1px; @@ -1307,15 +1290,18 @@ const StyledEditor = styled.div` } } } + .CodeMirror-scroll { position: relative; z-index: 0; width: 100%; } + .CodeMirror-code, .CodeMirror-gutters { padding-bottom: 32px; } + & .file-loading-widget { transform: translate3d(0, -100%, 0); } diff --git a/src/cloud/components/Topbar/SyncStatus.tsx b/src/cloud/components/Topbar/SyncStatus.tsx index 2f499219cf..a20cc5b4c1 100644 --- a/src/cloud/components/Topbar/SyncStatus.tsx +++ b/src/cloud/components/Topbar/SyncStatus.tsx @@ -4,29 +4,14 @@ import { ConnectionState } from '../../lib/editor/hooks/useRealtime' import Button from '../../../design/components/atoms/Button' import Spinner from '../../../design/components/atoms/Spinner' import styled from '../../../design/lib/styled' -import WithTooltip from '../../../design/components/atoms/WithTooltip' +import { mdiAlertOutline } from '@mdi/js' +import Icon from '../../../design/components/atoms/Icon' interface SyncStatusProps { provider: WebsocketProvider connState: ConnectionState } -const RECONNECTING_TOOLTIP = ( - <> - Attempting auto-reconnection -
- Changes will not be synced with the server until reconnection - -) - -const DISCONNECTED_TOOTIP = ( - <> - Please try reconnecting. -
- Changes will not be synced with the server until reconnection - -) - const SyncStatus = ({ provider, connState }: SyncStatusProps) => { const reconnect = useCallback(() => { provider.connect() @@ -36,38 +21,103 @@ const SyncStatus = ({ provider, connState }: SyncStatusProps) => { switch (connState) { case 'reconnecting': return ( - - - Connecting.. - - +
+
+ +
Connecting..
+
+
+ Changes will not be synced with the server until reconnection +
+
) case 'disconnected': return ( - - - + ) default: return undefined } }, [connState, reconnect]) - return {content} + return {content} } -const StyledSyncTextContainer = styled.div` - span { +const ConnectIssueContainer = styled.div` + position: absolute; + bottom: 40px; + right: 40px; + z-index: 9999; + width: 300px; + + background-color: ${({ theme }) => theme.colors.background.tertiary}; + border-left: solid 4px ${({ theme }) => theme.colors.variants.warning.base}; + border-radius: 4px; + + .sync__status__container_reconnecting { + height: 85px; + } + + .sync__status__container_disconnected { + height: 120px; + + .sync__status__reconnect_button { + height: 27px; + width: fit-content; + } + } + + .sync__status__container_reconnecting, + .sync__status__container_disconnected { display: flex; - align-items: center; - font-size: ${({ theme }) => theme.sizes.fonts.sm}px; + flex-direction: column; + gap: ${({ theme }) => theme.sizes.spaces.sm}px; + font-size: ${({ theme }) => theme.sizes.fonts.df}px; color: ${({ theme }) => theme.colors.variants.danger.text}; - > div { - margin-left: ${({ theme }) => theme.sizes.spaces.xsm}px; + padding: ${({ theme }) => theme.sizes.spaces.sm}px; + margin-left: ${({ theme }) => theme.sizes.spaces.sm}px; + } + + .sync__status__header { + display: flex; + gap: 8px; + align-items: center; + + .sync__status__header_warn_color { + color: ${({ theme }) => theme.colors.variants.warning.base}; } } + + .sync__status__header_text { + font-family: Lato sans-serif; + font-style: normal; + font-weight: bold; + font-size: 15px; + line-height: 18px; + } ` export default SyncStatus diff --git a/src/design/components/atoms/Spinner.tsx b/src/design/components/atoms/Spinner.tsx index c278e04606..9897bdfee8 100644 --- a/src/design/components/atoms/Spinner.tsx +++ b/src/design/components/atoms/Spinner.tsx @@ -13,7 +13,7 @@ const rotate = keyframes` ` interface SpinnerProps { - variant?: 'primary' | 'subtle' | 'secondary' + variant?: 'primary' | 'subtle' | 'secondary' | 'warning' size?: number style?: React.CSSProperties className?: string @@ -54,6 +54,10 @@ const SpinnerContainer = styled.div<{ size?: number }>` &.spinner--secondary { border-color: ${({ theme }) => theme.colors.variants.secondary.base}; } + + &.spinner--warning { + border-color: ${({ theme }) => theme.colors.variants.warning.base}; + } ` export default Spinner