Skip to content

Commit

Permalink
feat: fullscreen (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
nshenderov committed Nov 19, 2024
1 parent ed47409 commit ddb6db9
Show file tree
Hide file tree
Showing 10 changed files with 973 additions and 792 deletions.
79 changes: 79 additions & 0 deletions admin/src/components/Input/components/CKEReact.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useRef, useState } from 'react';
import { useField } from '@strapi/strapi/admin';
import { Box } from '@strapi/design-system';
import styled from 'styled-components';
import { ClassicEditor } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import 'ckeditor5/ckeditor5.css';

export const CKEReact = ({
name,
disabled,
maxLength,
preset,
setEditorInstance,
}) => {
const { onChange: fieldOnChange, value, error } = useField(name);

const [lengthMax, setLengthMax] = useState(false);

const wordCounter = useRef(null);

const handleCounter = (number) => setLengthMax(number > maxLength);

const hasWordCountPlugin = Boolean(preset.editorConfig.WordCountPlugin);

const onEditorReady = (editor) => {
if (hasWordCountPlugin) {
const wordCountPlugin = editor.plugins.get('WordCount');
wordCountPlugin.on('update', (evt, stats) =>
handleCounter(stats.characters)
);
const wordCountWrapper = wordCounter.current;
wordCountWrapper?.appendChild(wordCountPlugin.wordCountContainer);
}

if (editor.plugins.has('ImageUploadEditing')) {
editor.plugins
.get('ImageUploadEditing')
.on('uploadComplete', (evt, { data, imageElement }) =>
editor.model.change((writer) =>
writer.setAttribute('alt', data.alt, imageElement)
)
);
}

setEditorInstance(editor);
};

const onEditorChange = (event, editor) => {
const data = editor.getData();
fieldOnChange({ target: { name, value: data } });
};

return (
<>
<CKEditor
editor={ClassicEditor}
config={preset.editorConfig}
disabled={disabled}
data={value ?? ''}
onReady={onEditorReady}
onChange={onEditorChange}
/>
{hasWordCountPlugin && (
<WordCounterBox
color={lengthMax ? 'danger500' : 'neutral400'}
ref={wordCounter}
></WordCounterBox>
)}
</>
);
};

const WordCounterBox = styled(Box)`
display: flex;
width: 100%;
justify-content: flex-end;
align-items: center;
`;
139 changes: 40 additions & 99 deletions admin/src/components/Input/components/Editor.jsx
Original file line number Diff line number Diff line change
@@ -1,138 +1,79 @@
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { Loader } from '@strapi/design-system';
import { Box } from '@strapi/design-system';
import styled from 'styled-components';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import { ClassicEditor } from 'ckeditor5';
import { Box, Loader } from '@strapi/design-system';
import { useField } from '@strapi/strapi/admin';
import 'ckeditor5/ckeditor5.css';

import { CKEReact } from './CKEReact';
import { EditorLayout } from './EditorLayout';
import { MediaLib } from './MediaLib';
import { getConfiguredPreset } from '../config';
import { GlobalStyling } from './GlobalStyling';

const Wrapper = styled('div')`
${({ styles }) => styles}
`;

export const Editor = ({
name,
disabled,
presetName,
maxLength,
placeholder,
}) => {
const { onChange, value } = useField(name);

const [editorInstance, setEditorInstance] = useState(false);

const [mediaLibVisible, setMediaLibVisible] = useState(false);

const [editorInstance, setEditorInstance] = useState(false);
const [preset, setPreset] = useState(null);

const [lengthMax, setLengthMax] = useState(false);

const wordCounter = useRef(null);
const toggleMediaLib = () => setMediaLibVisible((prev) => !prev);

const handleToggleMediaLib = () => setMediaLibVisible((prev) => !prev);
const handleChangeAssets = (newElems) => {
const viewFragment = editorInstance.data.processor.toView(newElems);
const modelFragment = editorInstance.data.toModel(viewFragment);
editorInstance.model.insertContent(modelFragment);

const handleCounter = (number) =>
number > maxLength ? setLengthMax(true) : setLengthMax(false);
toggleMediaLib();
};

useEffect(() => {
(async () => {
const currentPreset = await getConfiguredPreset(presetName, {
toggleMediaLib: handleToggleMediaLib,
toggleMediaLib: toggleMediaLib,
strapiFieldPlaceholder: placeholder,
});

setPreset(currentPreset);
})();
}, []);

if (!preset) {
return (
<LoaderBox hasRadius background="neutral100">
<Loader>Loading...</Loader>
</LoaderBox>
);
}

return (
<>
{preset && <GlobalStyling />}
<Wrapper styles={preset?.styles}>
{!preset && (
<LoaderBox hasRadius background="neutral100">
<Loader>Loading...</Loader>
</LoaderBox>
)}
{preset && (
<>
<CKEditor
editor={ClassicEditor}
config={preset.editorConfig}
disabled={disabled}
data={value ?? ''}
onReady={(editor) => {
if (preset.editorConfig.WordCountPlugin) {
const wordCountPlugin = editor.plugins.get('WordCount');
wordCountPlugin.on('update', (evt, stats) =>
handleCounter(stats.characters)
);
const wordCountWrapper = wordCounter.current;
wordCountWrapper?.appendChild(
wordCountPlugin.wordCountContainer
);
}

if (editor.plugins.has('ImageUploadEditing')) {
editor.plugins
.get('ImageUploadEditing')
.on('uploadComplete', (evt, { data, imageElement }) =>
editor.model.change((writer) =>
writer.setAttribute('alt', data.alt, imageElement)
)
);
}

setEditorInstance(editor);
}}
onChange={(event, editor) => {
const data = editor.getData();
onChange({ target: { name, value: data } });
}}
/>
<MediaLib
isOpen={mediaLibVisible}
onToggle={handleToggleMediaLib}
editor={editorInstance}
/>
{preset.editorConfig.WordCountPlugin && (
<CounterLoaderBox
color={lengthMax ? 'danger500' : 'neutral400'}
ref={wordCounter}
>
{!editorInstance && <Loader small>Loading...</Loader>}
</CounterLoaderBox>
)}
</>
)}
</Wrapper>
<GlobalStyling />
<EditorLayout presetStyles={preset.styles}>
<CKEReact
name={name}
preset={preset}
disabled={disabled}
maxLength={maxLength}
setEditorInstance={setEditorInstance}
/>
<MediaLib
isOpen={mediaLibVisible}
toggle={toggleMediaLib}
handleChangeAssets={handleChangeAssets}
/>
</EditorLayout>
</>
);
};

Editor.propTypes = {
name: PropTypes.string.isRequired,
disabled: PropTypes.bool,
presetName: PropTypes.string.isRequired,
maxLength: PropTypes.number,
placeholder: PropTypes.string,
};

const CounterLoaderBox = styled(Box)`
display: flex;
width: 100%;
justify-content: flex-end;
align-items: center;
`;
const LoaderBox = styled(Box)`
display: flex;
height: 200px;
width: 100%;
justify-content: center;
align-items: center;
`;
height: 200px;
width: 100%;
`;
106 changes: 106 additions & 0 deletions admin/src/components/Input/components/EditorLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { useEffect, useState } from 'react';
import { Box, Flex, IconButton, FocusTrap, Portal } from '@strapi/design-system';
import { Expand, Collapse } from '@strapi/icons';
import styled from 'styled-components';

export const EditorLayout = ({ children, presetStyles }) => {
const [isExpandedMode, setIsExpandedMode] = useState(false);

const handleToggleExpand = () => setIsExpandedMode(true);
const handleOnCollapse = () => setIsExpandedMode(false);

useEffect(() => {
if (isExpandedMode) {
document.body.classList.add('lock-body-scroll');
}

return () => {
document.body.classList.remove('lock-body-scroll');
};
}, [isExpandedMode]);

if (isExpandedMode) {
return (
<Portal role="dialog" aria-modal={false}>
<FocusTrap onEscape={handleOnCollapse}>
<Backdrop
position="fixed"
top={0}
left={0}
right={0}
bottom={0}
zIndex={4}
justifyContent="center"
onClick={handleOnCollapse}
>
<FullScreenBox
background="neutral100"
hasRadius
shadow="popupShadow"
overflow="hidden"
width="90%"
height="90%"
onClick={(e) => e.stopPropagation()}
position="relative"
>
<Flex height="100%" alignItems="flex-start" direction="column">
<EditorWrapper
$presetStyles={presetStyles}
className={isExpandedMode ? 'ck-editor__expanded' : ''}
>
{children}
<CollapseButton
label="Collapse"
onClick={handleOnCollapse}
>
<Collapse />
</CollapseButton>
</EditorWrapper>
</Flex>
</FullScreenBox>
</Backdrop>
</FocusTrap>
</Portal>
);
}

return (
<EditorWrapper $presetStyles={presetStyles}>
{children}
<ExpandButton label="Expand" onClick={handleToggleExpand}>
<Expand />
</ExpandButton>
</EditorWrapper>
);
};

const EditorWrapper = styled('div')`
position: relative;
width: 100%;
height: 100%;
${({ $presetStyles }) => $presetStyles}
`;

const Backdrop = styled(Flex)`
// Background with 20% opacity
background: ${({ theme }) => `${theme.colors.neutral800}1F`};
`;

const ExpandButton = styled(IconButton)`
position: absolute;
bottom: 1.4rem;
right: 1.2rem;
z-index: 2;
`;

const CollapseButton = styled(IconButton)`
position: absolute;
bottom: 2.5rem;
right: 1.2rem;
z-index: 2;
`;

const FullScreenBox = styled(Box)`
max-width: var(--ck-editor-full-screen-box-max-width);
`
Loading

0 comments on commit ddb6db9

Please sign in to comment.