mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 22:23:42 +01:00
React IDE page shell (#14988)
* React IDE page shell * Set the maximum height of the symbol palette to 336px * Tidy export * Remove unnecessary destructuring * Update comment * Optimize toggle Co-authored-by: Alf Eaton <alf.eaton@overleaf.com> * Change snap-to-collapse threshold to 5% * Synchronize left column width between history and editor views and remove duplication in ide-page * Replace resizer dots with SVG * Rermove unnecessary import and comment the remaining ones * Use block prepend to avoid duplication * Improve vertical content divider styling * Implement fixed width during container resize on left column * Change IDE page file extension * Refactor fixed-size panel into a hook and use for chat panel --------- Co-authored-by: Alf Eaton <alf.eaton@overleaf.com> GitOrigin-RevId: aa881e48a2838a192b6f8f9e16e561f5cd706bd3
This commit is contained in:
committed by
Copybot
co-authored by
Alf Eaton
parent
afcac7af86
commit
ea1fc5f74e
Generated
+19
@@ -39625,6 +39625,16 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-resizable-panels": {
|
||||||
|
"version": "0.0.55",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz",
|
||||||
|
"integrity": "sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.14.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-resize-detector": {
|
"node_modules/react-resize-detector": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz",
|
||||||
@@ -48067,6 +48077,7 @@
|
|||||||
"react-i18next": "^11.18.6",
|
"react-i18next": "^11.18.6",
|
||||||
"react-linkify": "^1.0.0-alpha",
|
"react-linkify": "^1.0.0-alpha",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
|
"react-resizable-panels": "^0.0.55",
|
||||||
"react2angular": "^4.0.6",
|
"react2angular": "^4.0.6",
|
||||||
"react2angular-shared-context": "^1.1.0",
|
"react2angular-shared-context": "^1.1.0",
|
||||||
"requirejs": "^2.3.6",
|
"requirejs": "^2.3.6",
|
||||||
@@ -56670,6 +56681,7 @@
|
|||||||
"react-i18next": "^11.18.6",
|
"react-i18next": "^11.18.6",
|
||||||
"react-linkify": "^1.0.0-alpha",
|
"react-linkify": "^1.0.0-alpha",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
|
"react-resizable-panels": "^0.0.55",
|
||||||
"react2angular": "^4.0.6",
|
"react2angular": "^4.0.6",
|
||||||
"react2angular-shared-context": "^1.1.0",
|
"react2angular-shared-context": "^1.1.0",
|
||||||
"recurly": "^4.0.0",
|
"recurly": "^4.0.0",
|
||||||
@@ -81001,6 +81013,13 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||||
"integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ=="
|
"integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ=="
|
||||||
},
|
},
|
||||||
|
"react-resizable-panels": {
|
||||||
|
"version": "0.0.55",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz",
|
||||||
|
"integrity": "sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"react-resize-detector": {
|
"react-resize-detector": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz",
|
||||||
|
|||||||
@@ -873,9 +873,14 @@ const ProjectController = {
|
|||||||
!Features.hasFeature('saas') ||
|
!Features.hasFeature('saas') ||
|
||||||
req.query?.personal_access_token === 'true'
|
req.query?.personal_access_token === 'true'
|
||||||
|
|
||||||
|
const idePageReact = req.query?.['ide-page'] === 'react'
|
||||||
|
|
||||||
const template =
|
const template =
|
||||||
detachRole === 'detached'
|
detachRole === 'detached'
|
||||||
? 'project/editor_detached'
|
? // TODO: Create React version of detached page
|
||||||
|
'project/editor_detached'
|
||||||
|
: idePageReact
|
||||||
|
? 'project/ide-react'
|
||||||
: 'project/editor'
|
: 'project/editor'
|
||||||
|
|
||||||
res.render(template, {
|
res.render(template, {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ meta(name="ol-hasTrackChangesFeature", data-type="boolean" content=hasTrackChang
|
|||||||
meta(name="ol-mathJax3Path" content=mathJax3Path)
|
meta(name="ol-mathJax3Path" content=mathJax3Path)
|
||||||
meta(name="ol-completedTutorials", data-type="json" content=user.completedTutorials)
|
meta(name="ol-completedTutorials", data-type="json" content=user.completedTutorials)
|
||||||
meta(name="ol-projectTags" data-type="json" content=projectTags)
|
meta(name="ol-projectTags" data-type="json" content=projectTags)
|
||||||
|
meta(name="ol-idePageReact", data-type="boolean" content=idePageReact)
|
||||||
|
|
||||||
- var fileActionI18n = ['edited', 'renamed', 'created', 'deleted'].reduce((acc, i) => {acc[i] = translate('file_action_' + i); return acc}, {})
|
- var fileActionI18n = ['edited', 'renamed', 'created', 'deleted'].reduce((acc, i) => {acc[i] = translate('file_action_' + i); return acc}, {})
|
||||||
meta(name="ol-fileActionI18n" data-type="json" content=fileActionI18n)
|
meta(name="ol-fileActionI18n" data-type="json" content=fileActionI18n)
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
extends ../layout
|
||||||
|
|
||||||
|
block vars
|
||||||
|
- var suppressNavbar = true
|
||||||
|
- var suppressFooter = true
|
||||||
|
- var suppressSkipToContent = true
|
||||||
|
- metadata.robotsNoindexNofollow = true
|
||||||
|
|
||||||
|
block entrypointVar
|
||||||
|
- entrypoint = 'pages/ide'
|
||||||
|
|
||||||
|
block content
|
||||||
|
main#ide-root
|
||||||
|
|
||||||
|
block append meta
|
||||||
|
include ./editor/meta
|
||||||
|
|
||||||
|
block prepend foot-scripts
|
||||||
|
each file in (useOpenTelemetry ? entrypointScripts("tracing") : [])
|
||||||
|
script(type="text/javascript", nonce=scriptNonce, src=file)
|
||||||
|
script(type="text/javascript", nonce=scriptNonce, src=(wsUrl || '/socket.io') + '/socket.io.js')
|
||||||
@@ -926,6 +926,7 @@
|
|||||||
"resend_group_invite": "",
|
"resend_group_invite": "",
|
||||||
"resend_managed_user_invite": "",
|
"resend_managed_user_invite": "",
|
||||||
"resending_confirmation_email": "",
|
"resending_confirmation_email": "",
|
||||||
|
"resize": "",
|
||||||
"resolve": "",
|
"resolve": "",
|
||||||
"resolved_comments": "",
|
"resolved_comments": "",
|
||||||
"restore_file": "",
|
"restore_file": "",
|
||||||
@@ -1178,6 +1179,10 @@
|
|||||||
"toolbar_table_insert_table_lowercase": "",
|
"toolbar_table_insert_table_lowercase": "",
|
||||||
"toolbar_toggle_symbol_palette": "",
|
"toolbar_toggle_symbol_palette": "",
|
||||||
"toolbar_undo": "",
|
"toolbar_undo": "",
|
||||||
|
"tooltip_hide_filetree": "",
|
||||||
|
"tooltip_hide_pdf": "",
|
||||||
|
"tooltip_show_filetree": "",
|
||||||
|
"tooltip_show_pdf": "",
|
||||||
"total_per_month": "",
|
"total_per_month": "",
|
||||||
"total_per_year": "",
|
"total_per_year": "",
|
||||||
"total_with_subtotal_and_tax": "",
|
"total_with_subtotal_and_tax": "",
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
|
||||||
|
import { GenericErrorBoundaryFallback } from '@/shared/components/generic-error-boundary-fallback'
|
||||||
|
import withErrorBoundary from '@/infrastructure/error-boundary'
|
||||||
|
import IdePage from '@/features/ide-react/components/layout/ide-page'
|
||||||
|
|
||||||
|
function IdeRoot() {
|
||||||
|
// Check that we haven't inadvertently loaded Angular
|
||||||
|
// TODO: Remove this before rolling out this component to any users
|
||||||
|
if (typeof window.angular !== 'undefined') {
|
||||||
|
throw new Error('Angular detected. This page must not load Angular.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isReady } = useWaitForI18n()
|
||||||
|
|
||||||
|
if (!isReady) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <IdePage />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withErrorBoundary(IdeRoot, GenericErrorBoundaryFallback)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import LayoutWithPlaceholders from '@/features/ide-react/components/layout/layout-with-placeholders'
|
||||||
|
|
||||||
|
// This is filled with placeholder content while the real content is migrated
|
||||||
|
// away from Angular
|
||||||
|
export default function IdePage() {
|
||||||
|
return (
|
||||||
|
<div id="ide-react-page">
|
||||||
|
<LayoutWithPlaceholders shouldPersistLayout />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import PlaceholderHeader from '@/features/ide-react/components/layout/placeholder/placeholder-header'
|
||||||
|
import PlaceholderChat from '@/features/ide-react/components/layout/placeholder/placeholder-chat'
|
||||||
|
import PlaceholderHistory from '@/features/ide-react/components/layout/placeholder/placeholder-history'
|
||||||
|
import PlaceholderEditorMainContent from '@/features/ide-react/components/layout/placeholder/placeholder-editor-main-content'
|
||||||
|
import MainLayout from '@/features/ide-react/components/layout/main-layout'
|
||||||
|
|
||||||
|
export default function LayoutWithPlaceholders({
|
||||||
|
shouldPersistLayout,
|
||||||
|
}: {
|
||||||
|
shouldPersistLayout: boolean
|
||||||
|
}) {
|
||||||
|
const [chatIsOpen, setChatIsOpen] = useState(false)
|
||||||
|
const [historyIsOpen, setHistoryIsOpen] = useState(false)
|
||||||
|
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
|
||||||
|
|
||||||
|
const headerContent = (
|
||||||
|
<PlaceholderHeader
|
||||||
|
chatIsOpen={chatIsOpen}
|
||||||
|
setChatIsOpen={setChatIsOpen}
|
||||||
|
historyIsOpen={historyIsOpen}
|
||||||
|
setHistoryIsOpen={setHistoryIsOpen}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const chatContent = <PlaceholderChat />
|
||||||
|
const mainContent = historyIsOpen ? (
|
||||||
|
<PlaceholderHistory
|
||||||
|
shouldPersistLayout
|
||||||
|
leftColumnDefaultSize={leftColumnDefaultSize}
|
||||||
|
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PlaceholderEditorMainContent
|
||||||
|
shouldPersistLayout
|
||||||
|
leftColumnDefaultSize={leftColumnDefaultSize}
|
||||||
|
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MainLayout
|
||||||
|
headerContent={headerContent}
|
||||||
|
chatContent={chatContent}
|
||||||
|
mainContent={mainContent}
|
||||||
|
chatIsOpen={chatIsOpen}
|
||||||
|
shouldPersistLayout={shouldPersistLayout}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Panel, PanelGroup } from 'react-resizable-panels'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
import { HorizontalResizeHandle } from '../resize/horizontal-resize-handle'
|
||||||
|
import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column'
|
||||||
|
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||||
|
|
||||||
|
const CHAT_DEFAULT_SIZE = 20
|
||||||
|
|
||||||
|
type PageProps = {
|
||||||
|
headerContent: ReactNode
|
||||||
|
chatContent: ReactNode
|
||||||
|
mainContent: ReactNode
|
||||||
|
chatIsOpen: boolean
|
||||||
|
shouldPersistLayout: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main area below the header is split into two: the main content and chat.
|
||||||
|
// The reason for not splitting the left column containing the file tree and
|
||||||
|
// outline here is that the history view has its own file tree, so it is more
|
||||||
|
// convenient to replace the whole of the main content when in history view.
|
||||||
|
export default function MainLayout({
|
||||||
|
headerContent,
|
||||||
|
chatContent,
|
||||||
|
mainContent,
|
||||||
|
chatIsOpen,
|
||||||
|
shouldPersistLayout,
|
||||||
|
}: PageProps) {
|
||||||
|
const { fixedPanelRef: chatPanelRef, handleLayout } = useFixedSizeColumn(
|
||||||
|
CHAT_DEFAULT_SIZE,
|
||||||
|
chatIsOpen
|
||||||
|
)
|
||||||
|
|
||||||
|
useCollapsiblePanel(chatIsOpen, chatPanelRef)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ide-react-main">
|
||||||
|
{headerContent}
|
||||||
|
<div className="ide-react-body">
|
||||||
|
<PanelGroup
|
||||||
|
autoSaveId={shouldPersistLayout ? 'ide-react-chat-layout' : undefined}
|
||||||
|
direction="horizontal"
|
||||||
|
onLayout={handleLayout}
|
||||||
|
>
|
||||||
|
<Panel id="main" order={1}>
|
||||||
|
{mainContent}
|
||||||
|
</Panel>
|
||||||
|
{chatIsOpen ? (
|
||||||
|
<>
|
||||||
|
<HorizontalResizeHandle />
|
||||||
|
<Panel
|
||||||
|
ref={chatPanelRef}
|
||||||
|
id="chat"
|
||||||
|
order={2}
|
||||||
|
defaultSize={CHAT_DEFAULT_SIZE}
|
||||||
|
minSize={5}
|
||||||
|
collapsible
|
||||||
|
>
|
||||||
|
{chatContent}
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</PanelGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default function PlaceholderChat() {
|
||||||
|
return (
|
||||||
|
<aside className="chat ide-react-placeholder-chat">Chat placeholder</aside>
|
||||||
|
)
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
import React, { useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
ImperativePanelHandle,
|
||||||
|
Panel,
|
||||||
|
PanelGroup,
|
||||||
|
} from 'react-resizable-panels'
|
||||||
|
import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
|
||||||
|
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
|
||||||
|
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
|
||||||
|
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||||
|
|
||||||
|
type PlaceholderEditorAndPdfProps = {
|
||||||
|
shouldPersistLayout?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlaceholderEditorAndPdf({
|
||||||
|
shouldPersistLayout = false,
|
||||||
|
}: PlaceholderEditorAndPdfProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [pdfIsOpen, setPdfIsOpen] = useState(false)
|
||||||
|
const [symbolPaletteIsOpen, setSymbolPaletteIsOpen] = useState(false)
|
||||||
|
|
||||||
|
const pdfPanelRef = useRef<ImperativePanelHandle>(null)
|
||||||
|
useCollapsiblePanel(pdfIsOpen, pdfPanelRef)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelGroup
|
||||||
|
autoSaveId={
|
||||||
|
shouldPersistLayout ? 'ide-react-editor-and-pdf-layout' : undefined
|
||||||
|
}
|
||||||
|
direction="horizontal"
|
||||||
|
>
|
||||||
|
<Panel defaultSize={50}>
|
||||||
|
<PanelGroup
|
||||||
|
autoSaveId={
|
||||||
|
shouldPersistLayout
|
||||||
|
? 'ide-react-editor-and-symbol-palette-layout'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
direction="vertical"
|
||||||
|
units="pixels"
|
||||||
|
>
|
||||||
|
<Panel id="editor" order={1}>
|
||||||
|
Editor placeholder
|
||||||
|
<br />
|
||||||
|
<button onClick={() => setSymbolPaletteIsOpen(value => !value)}>
|
||||||
|
Toggle symbol palette
|
||||||
|
</button>
|
||||||
|
</Panel>
|
||||||
|
{symbolPaletteIsOpen ? (
|
||||||
|
<>
|
||||||
|
<VerticalResizeHandle id="editor-symbol-palette" />
|
||||||
|
<Panel
|
||||||
|
id="symbol-palette"
|
||||||
|
order={2}
|
||||||
|
defaultSize={250}
|
||||||
|
minSize={250}
|
||||||
|
maxSize={336}
|
||||||
|
>
|
||||||
|
<div className="ide-react-placeholder-symbol-palette ">
|
||||||
|
Symbol palette placeholder
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</PanelGroup>
|
||||||
|
</Panel>
|
||||||
|
<HorizontalResizeHandle>
|
||||||
|
<HorizontalToggler
|
||||||
|
id="editor-pdf"
|
||||||
|
togglerType="east"
|
||||||
|
isOpen={pdfIsOpen}
|
||||||
|
setIsOpen={pdfIsOpen => setPdfIsOpen(pdfIsOpen)}
|
||||||
|
tooltipWhenOpen={t('tooltip_hide_pdf')}
|
||||||
|
tooltipWhenClosed={t('tooltip_show_pdf')}
|
||||||
|
/>
|
||||||
|
</HorizontalResizeHandle>
|
||||||
|
<Panel
|
||||||
|
ref={pdfPanelRef}
|
||||||
|
defaultSize={50}
|
||||||
|
minSize={5}
|
||||||
|
collapsible
|
||||||
|
onCollapse={collapsed => setPdfIsOpen(!collapsed)}
|
||||||
|
>
|
||||||
|
PDF placeholder
|
||||||
|
</Panel>
|
||||||
|
</PanelGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import TwoColumnMainContent from '@/features/ide-react/components/layout/two-column-main-content'
|
||||||
|
import PlaceholderEditorAndPdf from '@/features/ide-react/components/layout/placeholder/placeholder-editor-and-pdf'
|
||||||
|
import PlaceholderEditorSidebar from '@/features/ide-react/components/layout/placeholder/placeholder-editor-sidebar'
|
||||||
|
|
||||||
|
type PlaceholderEditorMainContentProps = {
|
||||||
|
shouldPersistLayout: boolean
|
||||||
|
leftColumnDefaultSize: number
|
||||||
|
setLeftColumnDefaultSize: React.Dispatch<React.SetStateAction<number>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlaceholderEditorMainContent({
|
||||||
|
shouldPersistLayout,
|
||||||
|
leftColumnDefaultSize,
|
||||||
|
setLeftColumnDefaultSize,
|
||||||
|
}: PlaceholderEditorMainContentProps) {
|
||||||
|
const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true)
|
||||||
|
|
||||||
|
const leftColumnContent = (
|
||||||
|
<PlaceholderEditorSidebar shouldPersistLayout={shouldPersistLayout} />
|
||||||
|
)
|
||||||
|
const rightColumnContent = (
|
||||||
|
<PlaceholderEditorAndPdf shouldPersistLayout={shouldPersistLayout} />
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TwoColumnMainContent
|
||||||
|
leftColumnId="editor-left-column"
|
||||||
|
leftColumnContent={leftColumnContent}
|
||||||
|
leftColumnDefaultSize={leftColumnDefaultSize}
|
||||||
|
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
|
||||||
|
rightColumnContent={rightColumnContent}
|
||||||
|
leftColumnIsOpen={leftColumnIsOpen}
|
||||||
|
setLeftColumnIsOpen={setLeftColumnIsOpen}
|
||||||
|
shouldPersistLayout={shouldPersistLayout}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Panel, PanelGroup } from 'react-resizable-panels'
|
||||||
|
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
|
||||||
|
|
||||||
|
type PlaceholderHeaderProps = {
|
||||||
|
shouldPersistLayout: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlaceholderEditorSidebar({
|
||||||
|
shouldPersistLayout,
|
||||||
|
}: PlaceholderHeaderProps) {
|
||||||
|
return (
|
||||||
|
<aside className="ide-react-placeholder-editor-sidebar">
|
||||||
|
<PanelGroup
|
||||||
|
autoSaveId={
|
||||||
|
shouldPersistLayout ? 'ide-react-editor-sidebar-layout' : undefined
|
||||||
|
}
|
||||||
|
direction="vertical"
|
||||||
|
>
|
||||||
|
<Panel defaultSize={75}>File tree placeholder</Panel>
|
||||||
|
<VerticalResizeHandle />
|
||||||
|
<Panel defaultSize={25}>File outline placeholder</Panel>
|
||||||
|
</PanelGroup>
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ChatToggleButton from '@/features/editor-navigation-toolbar/components/chat-toggle-button'
|
||||||
|
import HistoryToggleButton from '@/features/editor-navigation-toolbar/components/history-toggle-button'
|
||||||
|
|
||||||
|
type PlaceholderHeaderProps = {
|
||||||
|
chatIsOpen: boolean
|
||||||
|
setChatIsOpen: (chatIsOpen: boolean) => void
|
||||||
|
historyIsOpen: boolean
|
||||||
|
setHistoryIsOpen: (chatIsOpen: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlaceholderHeader({
|
||||||
|
chatIsOpen,
|
||||||
|
setChatIsOpen,
|
||||||
|
historyIsOpen,
|
||||||
|
setHistoryIsOpen,
|
||||||
|
}: PlaceholderHeaderProps) {
|
||||||
|
function toggleChatOpen() {
|
||||||
|
setChatIsOpen(!chatIsOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleHistoryOpen() {
|
||||||
|
setHistoryIsOpen(!historyIsOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="toolbar toolbar-header">
|
||||||
|
<div className="toolbar-left">Header placeholder</div>
|
||||||
|
<div className="toolbar-right">
|
||||||
|
<HistoryToggleButton
|
||||||
|
historyIsOpen={historyIsOpen}
|
||||||
|
onClick={toggleHistoryOpen}
|
||||||
|
/>
|
||||||
|
<ChatToggleButton
|
||||||
|
chatIsOpen={chatIsOpen}
|
||||||
|
onClick={toggleChatOpen}
|
||||||
|
unreadMessageCount={0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import TwoColumnMainContent from '@/features/ide-react/components/layout/two-column-main-content'
|
||||||
|
|
||||||
|
type PlaceholderHistoryProps = {
|
||||||
|
shouldPersistLayout: boolean
|
||||||
|
leftColumnDefaultSize: number
|
||||||
|
setLeftColumnDefaultSize: React.Dispatch<React.SetStateAction<number>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlaceholderHistory({
|
||||||
|
shouldPersistLayout,
|
||||||
|
leftColumnDefaultSize,
|
||||||
|
setLeftColumnDefaultSize,
|
||||||
|
}: PlaceholderHistoryProps) {
|
||||||
|
const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true)
|
||||||
|
|
||||||
|
const leftColumnContent = (
|
||||||
|
<aside className="ide-react-placeholder-editor-sidebar">
|
||||||
|
History file tree placeholder
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
const rightColumnContent = (
|
||||||
|
<div>History document diff viewer and versions list placeholder</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TwoColumnMainContent
|
||||||
|
leftColumnId="editor-left-column"
|
||||||
|
leftColumnContent={leftColumnContent}
|
||||||
|
leftColumnDefaultSize={leftColumnDefaultSize}
|
||||||
|
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
|
||||||
|
rightColumnContent={rightColumnContent}
|
||||||
|
leftColumnIsOpen={leftColumnIsOpen}
|
||||||
|
setLeftColumnIsOpen={setLeftColumnIsOpen}
|
||||||
|
shouldPersistLayout={shouldPersistLayout}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
import React, { ReactNode, useEffect } from 'react'
|
||||||
|
import { Panel, PanelGroup } from 'react-resizable-panels'
|
||||||
|
import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
|
||||||
|
import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column'
|
||||||
|
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||||
|
|
||||||
|
type TwoColumnMainContentProps = {
|
||||||
|
leftColumnId: string
|
||||||
|
leftColumnContent: ReactNode
|
||||||
|
leftColumnDefaultSize: number
|
||||||
|
setLeftColumnDefaultSize: React.Dispatch<React.SetStateAction<number>>
|
||||||
|
rightColumnContent: ReactNode
|
||||||
|
leftColumnIsOpen: boolean
|
||||||
|
setLeftColumnIsOpen: (
|
||||||
|
leftColumnIsOpen: TwoColumnMainContentProps['leftColumnIsOpen']
|
||||||
|
) => void
|
||||||
|
shouldPersistLayout?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TwoColumnMainContent({
|
||||||
|
leftColumnId,
|
||||||
|
leftColumnContent,
|
||||||
|
leftColumnDefaultSize,
|
||||||
|
setLeftColumnDefaultSize,
|
||||||
|
rightColumnContent,
|
||||||
|
leftColumnIsOpen,
|
||||||
|
setLeftColumnIsOpen,
|
||||||
|
shouldPersistLayout = false,
|
||||||
|
}: TwoColumnMainContentProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const {
|
||||||
|
fixedPanelRef: leftColumnPanelRef,
|
||||||
|
fixedPanelWidthRef: leftColumnWidthRef,
|
||||||
|
handleLayout,
|
||||||
|
} = useFixedSizeColumn(leftColumnDefaultSize, leftColumnIsOpen)
|
||||||
|
|
||||||
|
useCollapsiblePanel(leftColumnIsOpen, leftColumnPanelRef)
|
||||||
|
|
||||||
|
// Update the left column default size on unmount rather than doing it on
|
||||||
|
// every resize, which causes ResizeObserver errors
|
||||||
|
useEffect(() => {
|
||||||
|
if (leftColumnWidthRef.current) {
|
||||||
|
setLeftColumnDefaultSize(leftColumnWidthRef.current.size)
|
||||||
|
}
|
||||||
|
}, [leftColumnWidthRef, setLeftColumnDefaultSize])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelGroup
|
||||||
|
autoSaveId={
|
||||||
|
shouldPersistLayout ? 'ide-react-main-content-layout' : undefined
|
||||||
|
}
|
||||||
|
direction="horizontal"
|
||||||
|
onLayout={handleLayout}
|
||||||
|
>
|
||||||
|
<Panel
|
||||||
|
ref={leftColumnPanelRef}
|
||||||
|
defaultSize={leftColumnDefaultSize}
|
||||||
|
minSize={5}
|
||||||
|
collapsible
|
||||||
|
onCollapse={collapsed => setLeftColumnIsOpen(!collapsed)}
|
||||||
|
>
|
||||||
|
{leftColumnIsOpen ? leftColumnContent : null}
|
||||||
|
</Panel>
|
||||||
|
<HorizontalResizeHandle>
|
||||||
|
<HorizontalToggler
|
||||||
|
id={leftColumnId}
|
||||||
|
togglerType="west"
|
||||||
|
isOpen={leftColumnIsOpen}
|
||||||
|
setIsOpen={isOpen => setLeftColumnIsOpen(isOpen)}
|
||||||
|
tooltipWhenOpen={t('tooltip_hide_filetree')}
|
||||||
|
tooltipWhenClosed={t('tooltip_show_filetree')}
|
||||||
|
/>
|
||||||
|
</HorizontalResizeHandle>
|
||||||
|
<Panel>{rightColumnContent}</Panel>
|
||||||
|
</PanelGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { PanelResizeHandle } from 'react-resizable-panels'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { PanelResizeHandleProps } from 'react-resizable-panels/dist/declarations/src/PanelResizeHandle'
|
||||||
|
|
||||||
|
export const HorizontalResizeHandle: FC<PanelResizeHandleProps> = ({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelResizeHandle {...props}>
|
||||||
|
<div className="horizontal-resize-handle" title={t('resize')}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</PanelResizeHandle>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import Tooltip from '@/shared/components/tooltip'
|
||||||
|
|
||||||
|
type HorizontalTogglerType = 'west' | 'east'
|
||||||
|
|
||||||
|
type HorizontalTogglerProps = {
|
||||||
|
id: string
|
||||||
|
isOpen: boolean
|
||||||
|
setIsOpen: (isClosed: boolean) => void
|
||||||
|
togglerType: HorizontalTogglerType
|
||||||
|
tooltipWhenOpen: string
|
||||||
|
tooltipWhenClosed: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HorizontalToggler({
|
||||||
|
id,
|
||||||
|
isOpen,
|
||||||
|
setIsOpen,
|
||||||
|
togglerType,
|
||||||
|
tooltipWhenOpen,
|
||||||
|
tooltipWhenClosed,
|
||||||
|
}: HorizontalTogglerProps) {
|
||||||
|
const description = isOpen ? tooltipWhenOpen : tooltipWhenClosed
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
id={id}
|
||||||
|
description={description}
|
||||||
|
overlayProps={{
|
||||||
|
placement: togglerType === 'east' ? 'left' : 'right',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
'custom-toggler',
|
||||||
|
`custom-toggler-${togglerType}`,
|
||||||
|
{
|
||||||
|
'custom-toggler-open': isOpen,
|
||||||
|
'custom-toggler-closed': !isOpen,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
aria-label={description}
|
||||||
|
title=""
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
import { PanelResizeHandle } from 'react-resizable-panels'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { PanelResizeHandleProps } from 'react-resizable-panels/dist/declarations/src/PanelResizeHandle'
|
||||||
|
|
||||||
|
export function VerticalResizeHandle(props: PanelResizeHandleProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelResizeHandle {...props}>
|
||||||
|
<div className="vertical-resize-handle" title={t('resize')} />
|
||||||
|
</PanelResizeHandle>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { RefObject, useEffect } from 'react'
|
||||||
|
import { ImperativePanelHandle } from 'react-resizable-panels'
|
||||||
|
|
||||||
|
export default function useCollapsiblePanel(
|
||||||
|
panelIsOpen: boolean,
|
||||||
|
panelRef: RefObject<ImperativePanelHandle>
|
||||||
|
) {
|
||||||
|
useEffect(() => {
|
||||||
|
const panel = panelRef.current
|
||||||
|
if (!panel) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (panelIsOpen) {
|
||||||
|
panel.expand()
|
||||||
|
} else {
|
||||||
|
panel.collapse()
|
||||||
|
}
|
||||||
|
}, [panelIsOpen, panelRef])
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import { ImperativePanelHandle } from 'react-resizable-panels'
|
||||||
|
import { PanelGroupOnLayout } from 'react-resizable-panels/src/types'
|
||||||
|
|
||||||
|
export default function useFixedSizeColumn(
|
||||||
|
defaultSize: number,
|
||||||
|
isOpen: boolean
|
||||||
|
) {
|
||||||
|
const fixedPanelRef = useRef<ImperativePanelHandle>(null)
|
||||||
|
|
||||||
|
const fixedPanelWidthRef = useRef({ size: defaultSize, pixels: 0 })
|
||||||
|
const [initialLayoutDone, setInitialLayoutDone] = useState(false)
|
||||||
|
|
||||||
|
const measureFixedPanelSizePixels = useCallback(() => {
|
||||||
|
return fixedPanelRef.current?.getSize('pixels') || 0
|
||||||
|
}, [fixedPanelRef])
|
||||||
|
|
||||||
|
const handleLayout = useCallback(
|
||||||
|
sizes => {
|
||||||
|
// Measure the pixel width here because it's not always up to date in the
|
||||||
|
// panel's onResize
|
||||||
|
fixedPanelWidthRef.current = {
|
||||||
|
size: sizes[0],
|
||||||
|
pixels: measureFixedPanelSizePixels(),
|
||||||
|
}
|
||||||
|
setInitialLayoutDone(true)
|
||||||
|
},
|
||||||
|
[measureFixedPanelSizePixels]
|
||||||
|
) as PanelGroupOnLayout
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only start watching for resizes once the initial layout is done,
|
||||||
|
// otherwise we could measure the fixed column while it has zero width and
|
||||||
|
// collapse it
|
||||||
|
if (!initialLayoutDone || !fixedPanelRef.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixedPanelElement = document.querySelector(
|
||||||
|
`[data-panel-id="${fixedPanelRef.current.getId()}"]`
|
||||||
|
)
|
||||||
|
|
||||||
|
const panelGroupElement = fixedPanelElement?.closest('[data-panel-group]')
|
||||||
|
if (!panelGroupElement || !fixedPanelElement) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
fixedPanelRef.current?.resize(fixedPanelWidthRef.current.pixels, 'pixels')
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.observe(panelGroupElement)
|
||||||
|
|
||||||
|
return () => resizeObserver.unobserve(panelGroupElement)
|
||||||
|
}, [fixedPanelRef, measureFixedPanelSizePixels, initialLayoutDone, isOpen])
|
||||||
|
|
||||||
|
return {
|
||||||
|
fixedPanelRef,
|
||||||
|
fixedPanelWidthRef,
|
||||||
|
handleLayout,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Configure dynamically loaded assets (via webpack) to be downloaded from CDN
|
||||||
|
import '../utils/webpack-public-path'
|
||||||
|
|
||||||
|
// Set up error reporting, including Sentry
|
||||||
|
import '../infrastructure/error-reporter'
|
||||||
|
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import IdeRoot from '../features/ide-react/components/ide-root'
|
||||||
|
|
||||||
|
const element = document.getElementById('ide-root')
|
||||||
|
if (element) {
|
||||||
|
ReactDOM.render(<IdeRoot />, element)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import LayoutWithPlaceholders from '@/features/ide-react/components/layout/layout-with-placeholders'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Editor / Page Layout',
|
||||||
|
component: LayoutWithPlaceholders,
|
||||||
|
decorators: [
|
||||||
|
(Story: any) => (
|
||||||
|
<div
|
||||||
|
style={{ position: 'absolute', inset: '1em', border: 'solid #ccc 1px' }}
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Persisted = {
|
||||||
|
args: {
|
||||||
|
shouldPersistLayout: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Unpersisted = {
|
||||||
|
args: {
|
||||||
|
shouldPersistLayout: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
@import './editor/dictionary.less';
|
@import './editor/dictionary.less';
|
||||||
@import './editor/compile-button.less';
|
@import './editor/compile-button.less';
|
||||||
@import './editor/figure-modal.less';
|
@import './editor/figure-modal.less';
|
||||||
|
@import './editor/ide-react.less';
|
||||||
|
|
||||||
@ui-layout-toggler-def-height: 50px;
|
@ui-layout-toggler-def-height: 50px;
|
||||||
@ui-resizer-size: 7px;
|
@ui-resizer-size: 7px;
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#ide-react-page {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ide-react-main {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.toolbar.toolbar-header {
|
||||||
|
position: static;
|
||||||
|
flex-grow: 0;
|
||||||
|
color: var(--neutral-20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ide-react-body {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-resize-handle {
|
||||||
|
width: @ui-resizer-size !important;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
// Enable ::before and ::after pseudo-elements to position themselves correctly
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
background-color: @editor-resizer-bg-color;
|
||||||
|
|
||||||
|
.custom-toggler {
|
||||||
|
padding: 0;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
// This SVG has the colour hard-coded to the current value of @ol-blue-gray-2, so if we changed @ol-blue-gray-2,
|
||||||
|
// we'd have to change this SVG too
|
||||||
|
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='18' viewBox='0 0 7 18'%3E%3Cpath d='M2 0h3v3H2zM2 5h3v3H2zM2 10h3v3H2zM2 15h3v3H2z' style='fill:%239da7b7'/%3E%3C/svg%3E");
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
width: 7px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
top: 25%;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
top: 75%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-resize-handle {
|
||||||
|
height: 6px;
|
||||||
|
background-color: @vertical-resizable-resizer-bg;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: @vertical-resizable-resizer-hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
// This SVG has the colour hard-coded to the current value of @ol-blue-gray-2, so if we changed @ol-blue-gray-2,
|
||||||
|
// we'd have to change this SVG too
|
||||||
|
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='6' viewBox='0 0 18 6'%3E%3Cpath d='M0 1.5h3v3H0zM5 1.5h3v3H5zM10 1.5h3v3h-3zM15 1.5h3v3h-3z' style='fill:%239da7b7'/%3E%3C/svg%3E");
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styles for placeholder elements that will eventually be replaced
|
||||||
|
.ide-react-placeholder-chat {
|
||||||
|
background-color: var(--editor-toolbar-bg);
|
||||||
|
color: var(--neutral-20);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ide-react-placeholder-editor-sidebar {
|
||||||
|
height: 100%;
|
||||||
|
background-color: @file-tree-bg;
|
||||||
|
color: var(--neutral-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ide-react-placeholder-symbol-palette {
|
||||||
|
height: 100%;
|
||||||
|
background-color: @symbol-palette-bg;
|
||||||
|
color: var(--neutral-20);
|
||||||
|
}
|
||||||
@@ -1467,6 +1467,7 @@
|
|||||||
"resending_confirmation_email": "Resending confirmation email",
|
"resending_confirmation_email": "Resending confirmation email",
|
||||||
"reset_password": "Reset Password",
|
"reset_password": "Reset Password",
|
||||||
"reset_your_password": "Reset your password",
|
"reset_your_password": "Reset your password",
|
||||||
|
"resize": "Resize",
|
||||||
"resolve": "Resolve",
|
"resolve": "Resolve",
|
||||||
"resolved_comments": "Resolved comments",
|
"resolved_comments": "Resolved comments",
|
||||||
"restore": "Restore",
|
"restore": "Restore",
|
||||||
|
|||||||
@@ -330,6 +330,7 @@
|
|||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"react2angular": "^4.0.6",
|
"react2angular": "^4.0.6",
|
||||||
"react2angular-shared-context": "^1.1.0",
|
"react2angular-shared-context": "^1.1.0",
|
||||||
|
"react-resizable-panels": "^0.0.55",
|
||||||
"requirejs": "^2.3.6",
|
"requirejs": "^2.3.6",
|
||||||
"samlp": "^7.0.2",
|
"samlp": "^7.0.2",
|
||||||
"sandboxed-module": "overleaf/node-sandboxed-module#cafa2d60f17ce75cc023e6f296eb8de79d92d35d",
|
"sandboxed-module": "overleaf/node-sandboxed-module#cafa2d60f17ce75cc023e6f296eb8de79d92d35d",
|
||||||
|
|||||||
Reference in New Issue
Block a user