mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
refactor editos
This commit is contained in:
parent
03ddb1862c
commit
5e9c111e0f
149 changed files with 416 additions and 317 deletions
|
@ -1,42 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||
import { NodeEditorProps } from '~/types/node';
|
||||
import { useUploaderContext } from '~/utils/context/UploaderContextProvider';
|
||||
|
||||
import { AudioGrid } from '../AudioGrid';
|
||||
import { ImageGrid } from '../ImageGrid';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const AudioEditor: FC<NodeEditorProps> = () => {
|
||||
const {
|
||||
filesImages,
|
||||
filesAudios,
|
||||
uploadFiles,
|
||||
setImages,
|
||||
setAudios,
|
||||
pendingAudios,
|
||||
pendingImages,
|
||||
} = useUploaderContext()!;
|
||||
|
||||
return (
|
||||
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
|
||||
<div className={styles.wrap}>
|
||||
<ImageGrid
|
||||
files={filesImages}
|
||||
setFiles={setImages}
|
||||
locked={pendingImages}
|
||||
/>
|
||||
|
||||
<AudioGrid
|
||||
files={filesAudios}
|
||||
setFiles={setAudios}
|
||||
locked={pendingAudios}
|
||||
/>
|
||||
</div>
|
||||
</UploadDropzone>
|
||||
);
|
||||
};
|
||||
|
||||
export { AudioEditor };
|
|
@ -1,6 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
padding-bottom: $upload_button_height + $gap;
|
||||
min-height: 200px;
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import { SortableAudioGrid } from '~/components/sortable/SortableAudioGrid';
|
||||
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
||||
import { IFile } from '~/types';
|
||||
|
||||
interface IProps {
|
||||
files: IFile[];
|
||||
setFiles: (val: IFile[]) => void;
|
||||
locked: UploadStatus[];
|
||||
}
|
||||
|
||||
const AudioGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
||||
const onMove = useCallback(
|
||||
(newFiles: IFile[]) => {
|
||||
setFiles(newFiles);
|
||||
},
|
||||
[setFiles],
|
||||
);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(remove_id: IFile['id']) => {
|
||||
setFiles(files.filter((file) => file && file.id !== remove_id));
|
||||
},
|
||||
[setFiles, files],
|
||||
);
|
||||
|
||||
const onTitleChange = useCallback(
|
||||
(changeId: IFile['id'], title: string) => {
|
||||
setFiles(
|
||||
files.map((file) =>
|
||||
file && file.id === changeId
|
||||
? { ...file, metadata: { ...file.metadata, title } }
|
||||
: file,
|
||||
),
|
||||
);
|
||||
},
|
||||
[setFiles, files],
|
||||
);
|
||||
|
||||
return (
|
||||
<SortableAudioGrid
|
||||
onDelete={onDrop}
|
||||
onTitleChange={onTitleChange}
|
||||
onSortEnd={onMove}
|
||||
items={files}
|
||||
locked={locked}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { AudioGrid };
|
|
@ -1,24 +0,0 @@
|
|||
import React, { createElement, FC } from 'react';
|
||||
|
||||
import { NODE_PANEL_COMPONENTS } from '~/constants/node';
|
||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
||||
import { has } from '~/utils/ramda';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const EditorActionsPanel: FC = () => {
|
||||
const { values } = useNodeFormContext();
|
||||
|
||||
if (!values.type || !has(values.type, NODE_PANEL_COMPONENTS)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
{NODE_PANEL_COMPONENTS[values.type] &&
|
||||
NODE_PANEL_COMPONENTS[values.type].map((el, key) => createElement(el, { key }))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { EditorActionsPanel };
|
|
@ -1,30 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.panel {
|
||||
height: 72px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
bottom: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: $gap;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
pointer-events: none;
|
||||
touch-action: none;
|
||||
|
||||
& > * {
|
||||
margin: 0 $gap * 0.5;
|
||||
pointer-events: all;
|
||||
touch-action: auto;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { EditorUploadButton } from '~/components/editors/EditorUploadButton';
|
||||
import { UploadType } from '~/constants/uploads';
|
||||
import { IEditorComponentProps } from '~/types/node';
|
||||
|
||||
type IProps = IEditorComponentProps & {};
|
||||
|
||||
const EditorAudioUploadButton: FC<IProps> = () => (
|
||||
<EditorUploadButton
|
||||
accept="audio/*"
|
||||
icon="audio"
|
||||
type={UploadType.Audio}
|
||||
label="Добавить аудио"
|
||||
/>
|
||||
);
|
||||
|
||||
export { EditorAudioUploadButton };
|
|
@ -1,44 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { EditorActionsPanel } from '~/components/editors/EditorActionsPanel';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
||||
|
||||
const EditorButtons: FC = () => {
|
||||
const { values, handleChange, isSubmitting } = useNodeFormContext();
|
||||
const { isTablet } = useWindowSize();
|
||||
|
||||
return (
|
||||
<Padder style={{ position: 'relative' }}>
|
||||
<EditorActionsPanel />
|
||||
|
||||
<Group horizontal>
|
||||
<Filler>
|
||||
<InputText
|
||||
title="Название"
|
||||
value={values.title}
|
||||
handler={handleChange('title')}
|
||||
autoFocus={!isTablet}
|
||||
maxLength={256}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Filler>
|
||||
|
||||
<Button
|
||||
title={isTablet ? undefined : 'Сохранить'}
|
||||
iconRight="check"
|
||||
color={values.is_promoted ? 'flow' : 'lab'}
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
/>
|
||||
</Group>
|
||||
</Padder>
|
||||
);
|
||||
};
|
||||
|
||||
export { EditorButtons };
|
|
@ -1,37 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {
|
||||
onApprove: () => void;
|
||||
onDecline: () => void;
|
||||
}
|
||||
|
||||
const EditorConfirmClose: FC<IProps> = ({ onApprove, onDecline }) => (
|
||||
<div className={styles.wrap}>
|
||||
<Group className={styles.content}>
|
||||
<div className={styles.title}>Точно закрыть?</div>
|
||||
<div className={styles.subtitle}>
|
||||
Все изменения будут потеряны, воспоминания затёрты, очевидцы умрут, над миром воссияет
|
||||
ядерный рассвет.
|
||||
</div>
|
||||
|
||||
<div />
|
||||
|
||||
<Group horizontal>
|
||||
<Button color="gray" type="button" onClick={onApprove} autoFocus>
|
||||
Да
|
||||
</Button>
|
||||
|
||||
<Button type="button" onClick={onDecline}>
|
||||
О боже, нет!
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { EditorConfirmClose };
|
|
@ -1,42 +0,0 @@
|
|||
@import 'src/styles/variables.scss';
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.wrap {
|
||||
@include blur;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 21;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: appear 0.25s forwards;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-transform: uppercase;
|
||||
font: $font_18_semibold;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font: $font_12_medium;
|
||||
color: $gray_50;
|
||||
max-width: 300px;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { IEditorComponentProps } from '~/types/node';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type IProps = IEditorComponentProps & {};
|
||||
|
||||
const EditorFiller: FC<IProps> = () => <Filler className={styles.filler} />;
|
||||
|
||||
export { EditorFiller };
|
|
@ -1,4 +0,0 @@
|
|||
.filler {
|
||||
touch-action: none;
|
||||
pointer-events: none;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { EditorUploadButton } from '~/components/editors/EditorUploadButton';
|
||||
import { UploadType } from '~/constants/uploads';
|
||||
import { IEditorComponentProps } from '~/types/node';
|
||||
|
||||
type IProps = IEditorComponentProps & {};
|
||||
|
||||
const EditorImageUploadButton: FC<IProps> = () => (
|
||||
<EditorUploadButton
|
||||
accept="image/*"
|
||||
icon="image"
|
||||
type={UploadType.Image}
|
||||
label="Добавить фоточек"
|
||||
/>
|
||||
);
|
||||
|
||||
export { EditorImageUploadButton };
|
|
@ -1,45 +0,0 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
||||
import { IEditorComponentProps } from '~/types/node';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps extends IEditorComponentProps {}
|
||||
|
||||
const EditorPublicSwitch: FC<IProps> = () => {
|
||||
const { values, setFieldValue } = useNodeFormContext();
|
||||
|
||||
const onChange = useCallback(
|
||||
() => setFieldValue('is_promoted', !values.is_promoted),
|
||||
[values.is_promoted, setFieldValue],
|
||||
);
|
||||
|
||||
return (
|
||||
<Button
|
||||
color={values.is_promoted ? 'flow' : 'lab'}
|
||||
type="button"
|
||||
size="giant"
|
||||
label={
|
||||
values.is_promoted
|
||||
? 'Доступно всем на главной странице'
|
||||
: 'Видно только сотрудникам в лаборатории'
|
||||
}
|
||||
onClick={onChange}
|
||||
className={styles.button}
|
||||
round
|
||||
>
|
||||
{values.is_promoted ? (
|
||||
<Icon icon="waves" size={24} />
|
||||
) : (
|
||||
<div className={styles.lab_wrapper}>
|
||||
<Icon icon="lab" size={24} />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export { EditorPublicSwitch };
|
|
@ -1,63 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
@keyframes move_1 {
|
||||
0% {
|
||||
transform: scale(0) translate(0, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1) translate(5px, -5px);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.2) translate(-5px, -10px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes move_2 {
|
||||
0% {
|
||||
transform: scale(0) translate(0, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1) translate(-5px, -5px);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1.6) translate(5px, -10px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
|
||||
}
|
||||
|
||||
.lab_wrapper {
|
||||
position: relative;
|
||||
bottom: -2px;
|
||||
|
||||
.button:hover & {
|
||||
&:before,&:after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 10px;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
box-shadow: white 0 0 0 2px;
|
||||
border-radius: 4px;
|
||||
animation: move_1 0.5s infinite linear;
|
||||
}
|
||||
|
||||
&:after {
|
||||
animation: move_2 0.5s -0.25s infinite linear;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
import React, { ChangeEvent, FC, useCallback } from 'react';
|
||||
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { UploadType } from '~/constants/uploads';
|
||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
||||
import { IEditorComponentProps } from '~/types/node';
|
||||
import { useUploaderContext } from '~/utils/context/UploaderContextProvider';
|
||||
import { getFileType } from '~/utils/uploader';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type IProps = IEditorComponentProps & {
|
||||
accept?: string;
|
||||
icon?: string;
|
||||
type?: UploadType;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const EditorUploadButton: FC<IProps> = ({
|
||||
accept = 'image/*',
|
||||
icon = 'plus',
|
||||
type = UploadType.Image,
|
||||
label,
|
||||
}) => {
|
||||
const { uploadFiles } = useUploaderContext()!;
|
||||
const { values } = useNodeFormContext();
|
||||
|
||||
const onInputChange = useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const files = Array.from(event.target.files || []).filter(
|
||||
(file) => !type || getFileType(file) === type,
|
||||
);
|
||||
|
||||
uploadFiles(files);
|
||||
},
|
||||
[type, uploadFiles],
|
||||
);
|
||||
|
||||
const color = values.is_promoted ? 'flow' : 'lab';
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
round
|
||||
size="giant"
|
||||
className={styles.wrap}
|
||||
label={label}
|
||||
color={color}
|
||||
>
|
||||
<Icon icon={icon} size={24} />
|
||||
<input type="file" onChange={onInputChange} accept={accept} multiple />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export { EditorUploadButton };
|
|
@ -1,27 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
import React, { ChangeEvent, FC, useCallback, useEffect } from 'react';
|
||||
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { UploadSubject, UploadTarget, UploadType } from '~/constants/uploads';
|
||||
import { imagePresets } from '~/constants/urls';
|
||||
import { useUploader } from '~/hooks/data/useUploader';
|
||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
||||
import { IEditorComponentProps } from '~/types/node';
|
||||
import { getURL } from '~/utils/dom';
|
||||
import { getFileType } from '~/utils/uploader';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type IProps = IEditorComponentProps & {};
|
||||
|
||||
const EditorUploadCoverButton: FC<IProps> = () => {
|
||||
const { values, setFieldValue } = useNodeFormContext();
|
||||
const { uploadFile, files, pendingImages } = useUploader(
|
||||
UploadSubject.Editor,
|
||||
UploadTarget.Nodes,
|
||||
values.cover ? [values.cover] : [],
|
||||
);
|
||||
|
||||
const background = values.cover
|
||||
? getURL(values.cover, imagePresets['300'])
|
||||
: null;
|
||||
const preview = pendingImages?.[0]?.thumbnail || '';
|
||||
|
||||
const onDropCover = useCallback(() => {
|
||||
setFieldValue('cover', null);
|
||||
}, [setFieldValue]);
|
||||
|
||||
const onInputChange = useCallback(
|
||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(event.target.files || [])
|
||||
.filter((file) => getFileType(file) === UploadType.Image)
|
||||
.slice(0, 1);
|
||||
|
||||
const result = await uploadFile(files[0]);
|
||||
setFieldValue('cover', result);
|
||||
},
|
||||
[uploadFile, setFieldValue],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFieldValue('cover', files[files.length - 1]);
|
||||
}, [files, setFieldValue]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div
|
||||
className={styles.preview}
|
||||
style={{ backgroundImage: `url("${preview || background}")` }}
|
||||
>
|
||||
<div className={styles.input}>
|
||||
{!values.cover && <span>ОБЛОЖКА</span>}
|
||||
<input type="file" accept="image/*" onChange={onInputChange} />
|
||||
</div>
|
||||
|
||||
{values.cover && (
|
||||
<div className={styles.button} onClick={onDropCover}>
|
||||
<Icon icon="close" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { EditorUploadCoverButton };
|
|
@ -1,83 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.wrap {
|
||||
@include outer_shadow();
|
||||
|
||||
height: $upload_button_height;
|
||||
border-radius: ($upload_button_height * 0.5) !important;
|
||||
position: relative;
|
||||
border-radius: $radius;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.5s;
|
||||
background: $content_bg_lighter;
|
||||
flex: 0 1 $upload_button_height * 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font: $font_16_medium;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 0 1px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
border-radius: ($upload_button_height * 0.5) !important;
|
||||
background: 50% 50% no-repeat;
|
||||
background-size: cover;
|
||||
will-change: transform;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: $upload_button_height;
|
||||
flex: 0 0 $upload_button_height;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: inset rgba(255, 255, 255, 0.05) 1px 1px, rgba(0, 0, 0, 0.3) -1px 0;
|
||||
border-radius: $upload_button_height;
|
||||
background: $content_bg_lighter;
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: $color_danger;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { ImageGrid } from '~/components/editors/ImageGrid';
|
||||
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||
import { NodeEditorProps } from '~/types/node';
|
||||
import { useUploaderContext } from '~/utils/context/UploaderContextProvider';
|
||||
import { values } from '~/utils/ramda';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type IProps = NodeEditorProps;
|
||||
|
||||
const ImageEditor: FC<IProps> = () => {
|
||||
const { pending, files, setFiles, uploadFiles } = useUploaderContext()!;
|
||||
|
||||
return (
|
||||
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
|
||||
<div className={styles.wrap}>
|
||||
<ImageGrid files={files} setFiles={setFiles} locked={values(pending)} />
|
||||
</div>
|
||||
</UploadDropzone>
|
||||
);
|
||||
};
|
||||
|
||||
export { ImageEditor };
|
|
@ -1,9 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.wrap {
|
||||
min-height: 200px;
|
||||
padding-bottom: $upload_button_height + $gap;
|
||||
}
|
||||
|
||||
div.dropzone {
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import { SortableImageGrid } from '~/components/sortable/SortableImageGrid';
|
||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
||||
import { IFile } from '~/types';
|
||||
|
||||
interface IProps {
|
||||
files: IFile[];
|
||||
setFiles: (val: IFile[]) => void;
|
||||
locked: UploadStatus[];
|
||||
}
|
||||
|
||||
const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
||||
const { isTablet } = useWindowSize();
|
||||
|
||||
const onMove = useCallback(
|
||||
(newFiles: IFile[]) => {
|
||||
setFiles(newFiles.filter((it) => it));
|
||||
},
|
||||
[setFiles],
|
||||
);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(id: IFile['id']) => {
|
||||
setFiles(files.filter((file) => file && file.id !== id));
|
||||
},
|
||||
[setFiles, files],
|
||||
);
|
||||
|
||||
return (
|
||||
<SortableImageGrid
|
||||
onDelete={onDrop}
|
||||
onSortEnd={onMove}
|
||||
items={files}
|
||||
locked={locked}
|
||||
size={!isTablet ? 220 : (innerWidth - 60) / 2}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { ImageGrid };
|
|
@ -1,5 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.helper {
|
||||
opacity: 0.5;
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||
import { NodeEditorProps } from '~/types/node';
|
||||
import { useUploaderContext } from '~/utils/context/UploaderContextProvider';
|
||||
|
||||
import { AudioGrid } from '../AudioGrid';
|
||||
import { ImageGrid } from '../ImageGrid';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const RoomEditor: FC<NodeEditorProps> = () => {
|
||||
const {
|
||||
filesImages,
|
||||
filesAudios,
|
||||
uploadFiles,
|
||||
setImages,
|
||||
setAudios,
|
||||
pendingAudios,
|
||||
pendingImages,
|
||||
} = useUploaderContext()!;
|
||||
|
||||
return (
|
||||
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
|
||||
<div className={styles.wrap}>
|
||||
<ImageGrid
|
||||
files={filesImages}
|
||||
setFiles={setImages}
|
||||
locked={pendingImages}
|
||||
/>
|
||||
|
||||
<AudioGrid
|
||||
files={filesAudios}
|
||||
setFiles={setAudios}
|
||||
locked={pendingAudios}
|
||||
/>
|
||||
</div>
|
||||
</UploadDropzone>
|
||||
);
|
||||
};
|
||||
|
||||
export { RoomEditor };
|
|
@ -1,6 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
padding-bottom: $upload_button_height + $gap;
|
||||
min-height: 200px;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import { Textarea } from '~/components/input/Textarea';
|
||||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
||||
import { NodeEditorProps } from '~/types/node';
|
||||
import { path } from '~/utils/ramda';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type IProps = NodeEditorProps & {};
|
||||
|
||||
const TextEditor: FC<IProps> = () => {
|
||||
const { values, setFieldValue } = useNodeFormContext();
|
||||
const placeholder = useRandomPhrase('SIMPLE');
|
||||
|
||||
const setText = useCallback((text: string) => setFieldValue('blocks', [{ type: 'text', text }]), [
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
const text = (path(['blocks', 0, 'text'], values) as string) || '';
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Textarea value={text} handler={setText} minRows={6} placeholder={placeholder} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { TextEditor };
|
|
@ -1,7 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
& > div {
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import React, { FC, useCallback, useMemo } from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
||||
import { NodeEditorProps } from '~/types/node';
|
||||
import { getYoutubeThumb } from '~/utils/dom';
|
||||
import { path } from '~/utils/ramda';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type IProps = NodeEditorProps & {};
|
||||
|
||||
const VideoEditor: FC<IProps> = () => {
|
||||
const { values, setFieldValue } = useNodeFormContext();
|
||||
|
||||
const setUrl = useCallback((url: string) => setFieldValue('blocks', [{ type: 'video', url }]), [
|
||||
setFieldValue,
|
||||
]);
|
||||
|
||||
const url = (path(['blocks', 0, 'url'], values) as string) || '';
|
||||
const preview = useMemo(() => getYoutubeThumb(url), [url]);
|
||||
const backgroundImage = (preview && `url("${preview}")`) || '';
|
||||
|
||||
return (
|
||||
<div className={styles.preview} style={{ backgroundImage }}>
|
||||
<div className={styles.input_wrap}>
|
||||
<div className={classnames(styles.input, { active: !!preview })}>
|
||||
<InputText value={url} handler={setUrl} placeholder="Адрес видео" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { VideoEditor };
|
|
@ -1,38 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.preview {
|
||||
padding-top: 56.25%;
|
||||
position: relative;
|
||||
border-radius: $radius;
|
||||
background: 50% 50% no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.input_wrap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input {
|
||||
// @include outer_shadow();
|
||||
|
||||
flex: 1 0 50%;
|
||||
padding: $gap * 2;
|
||||
border-radius: $radius;
|
||||
background: $content_bg;
|
||||
margin: 20px;
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:global(.active) {
|
||||
background: $color_danger;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import React, { FC } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { Toggle } from '~/components/input/Toggle';
|
||||
import { FlowDisplayVariant } from '~/types';
|
||||
|
@ -60,7 +60,11 @@ const FlowCellMenu: FC<Props> = ({
|
|||
</div>
|
||||
|
||||
{hasDescription && (
|
||||
<Group className={styles.description} horizontal onClick={toggleViewDescription}>
|
||||
<Group
|
||||
className={styles.description}
|
||||
horizontal
|
||||
onClick={toggleViewDescription}
|
||||
>
|
||||
<Toggle color="white" value={descriptionEnabled} />
|
||||
<span>Текст</span>
|
||||
</Group>
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { FC, ReactElement } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Markdown } from '~/components/containers/Markdown';
|
||||
import { Markdown } from '~/components/common/Markdown';
|
||||
import { formatText } from '~/utils/dom';
|
||||
import { DivProps } from '~/utils/types';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { InfiniteScroll } from '~/components/containers/InfiniteScroll';
|
||||
import { InfiniteScroll } from '~/components/common/InfiniteScroll';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { INode } from '~/types';
|
||||
|
||||
|
@ -15,7 +15,12 @@ interface IProps {
|
|||
onLoadMore: () => void;
|
||||
}
|
||||
|
||||
const FlowSearchResults: FC<IProps> = ({ results, isLoading, onLoadMore, hasMore }) => {
|
||||
const FlowSearchResults: FC<IProps> = ({
|
||||
results,
|
||||
isLoading,
|
||||
onLoadMore,
|
||||
hasMore,
|
||||
}) => {
|
||||
if (!results.length) {
|
||||
return (
|
||||
<div className={styles.loading}>
|
||||
|
@ -28,7 +33,7 @@ const FlowSearchResults: FC<IProps> = ({ results, isLoading, onLoadMore, hasMore
|
|||
return (
|
||||
<div className={styles.wrap}>
|
||||
<InfiniteScroll hasMore={hasMore} loadMore={onLoadMore}>
|
||||
{results.map(node => (
|
||||
{results.map((node) => (
|
||||
<FlowRecentItem node={node} key={node.id} />
|
||||
))}
|
||||
</InfiniteScroll>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { LabSquare } from '~/components/lab/LabSquare';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
@ -15,8 +15,8 @@ const LabBanner: FC<IProps> = () => (
|
|||
<Group className={styles.content}>
|
||||
<p>
|
||||
<strong>
|
||||
Всё, что происходит здесь — всего лишь эксперимент, о котором не узнает никто за
|
||||
пределами Убежища.
|
||||
Всё, что происходит здесь — всего лишь эксперимент, о котором
|
||||
не узнает никто за пределами Убежища.
|
||||
</strong>
|
||||
</p>
|
||||
</Group>
|
||||
|
|
|
@ -2,9 +2,9 @@ import React, { FC, useCallback } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Grid } from '~/components/containers/Grid';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Grid } from '~/components/common/Grid';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { URLS } from '~/constants/urls';
|
||||
|
@ -21,14 +21,24 @@ type Props = {
|
|||
commentCount: number;
|
||||
};
|
||||
|
||||
const LabBottomPanel: FC<Props> = ({ node, hasNewComments, commentCount, isLoading }) => {
|
||||
const LabBottomPanel: FC<Props> = ({
|
||||
node,
|
||||
hasNewComments,
|
||||
commentCount,
|
||||
isLoading,
|
||||
}) => {
|
||||
const { push } = useNavigation();
|
||||
const onClick = useCallback(() => push(URLS.NODE_URL(node.id)), [push, node.id]);
|
||||
const onClick = useCallback(
|
||||
() => push(URLS.NODE_URL(node.id)),
|
||||
[push, node.id],
|
||||
);
|
||||
|
||||
return (
|
||||
<Group horizontal className={styles.wrap} onClick={onClick}>
|
||||
<div className={styles.timestamp}>
|
||||
<Placeholder active={isLoading}>{getPrettyDate(node.created_at)}</Placeholder>
|
||||
<Placeholder active={isLoading}>
|
||||
{getPrettyDate(node.created_at)}
|
||||
</Placeholder>
|
||||
</div>
|
||||
|
||||
<Filler />
|
||||
|
@ -37,7 +47,9 @@ const LabBottomPanel: FC<Props> = ({ node, hasNewComments, commentCount, isLoadi
|
|||
{commentCount > 0 && (
|
||||
<Grid
|
||||
horizontal
|
||||
className={classNames(styles.comments, { [styles.active]: hasNewComments })}
|
||||
className={classNames(styles.comments, {
|
||||
[styles.active]: hasNewComments,
|
||||
})}
|
||||
>
|
||||
<Icon icon={hasNewComments ? 'comment_new' : 'comment'} size={24} />
|
||||
<span>{commentCount}</span>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Markdown } from '~/components/containers/Markdown';
|
||||
import { Markdown } from '~/components/common/Markdown';
|
||||
import { Paragraph } from '~/components/placeholders/Paragraph';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
import { useGotoNode } from '~/hooks/node/useGotoNode';
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { useRef } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { SearchInput } from '~/components/input/SearchInput';
|
||||
import { HorizontalMenu } from '~/components/menu/HorizontalMenu';
|
||||
import { LabNodesSort } from '~/types/lab';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { URLS } from '~/constants/urls';
|
||||
|
@ -44,7 +44,9 @@ const LabHero: FC<IProps> = ({ node, isLoading }) => {
|
|||
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title}>{node.title}</div>
|
||||
<div className={styles.description}>{getPrettyDate(node.created_at)}</div>
|
||||
<div className={styles.description}>
|
||||
{getPrettyDate(node.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { LabHero } from '~/components/lab/LabHero';
|
||||
import styles from '~/containers/lab/LabStats/styles.module.scss';
|
||||
import { INode } from '~/types';
|
||||
|
@ -16,7 +16,7 @@ const LabHeroes: FC<IProps> = ({ nodes, isLoading }) => {
|
|||
if (isLoading) {
|
||||
return (
|
||||
<Group className={styles.heroes}>
|
||||
{empty.map(i => (
|
||||
{empty.map((i) => (
|
||||
<LabHero isLoading key={i} />
|
||||
))}
|
||||
</Group>
|
||||
|
@ -25,7 +25,7 @@ const LabHeroes: FC<IProps> = ({ nodes, isLoading }) => {
|
|||
|
||||
return (
|
||||
<Group className={styles.heroes}>
|
||||
{nodes.slice(0, 10).map(node => (
|
||||
{nodes.slice(0, 10).map((node) => (
|
||||
<LabHero node={node} key={node?.id} />
|
||||
))}
|
||||
</Group>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { VFC } from 'react';
|
||||
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Card } from '~/components/common/Card';
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { FC } from 'react';
|
|||
|
||||
import Tippy from '@tippyjs/react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { Markdown } from '~/components/containers/Markdown';
|
||||
import { Markdown } from '~/components/common/Markdown';
|
||||
import { Paragraph } from '~/components/placeholders/Paragraph';
|
||||
import { NodeComponentProps } from '~/constants/node';
|
||||
import { useGotoNode } from '~/hooks/node/useGotoNode';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { Avatar } from '~/components/common/Avatar';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { imagePresets } from '~/constants/urls';
|
||||
import { IFile } from '~/types';
|
||||
import { getURL } from '~/utils/dom';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Avatar } from '~/components/common/Avatar';
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Card } from '~/components/common/Card';
|
||||
import { useUserDescription } from '~/hooks/auth/useUserDescription';
|
||||
import { INodeUser } from '~/types';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { FC, useMemo } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { ERRORS } from '~/constants/errors';
|
||||
import { t } from '~/utils/trans';
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { FC, ReactElement } from 'react';
|
||||
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Hoverable } from '~/components/common/Hoverable';
|
||||
import { SubTitle } from '~/components/common/SubTitle';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { NodeThumbnail } from '~/components/node/NodeThumbnail';
|
||||
import { INode } from '~/types';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { FC, memo } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import cell_style from '~/components/node/NodeThumbnail/styles.module.scss';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { range } from '~/utils/ramda';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useCallback, useState, VFC } from 'react';
|
||||
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Markdown } from '~/components/containers/Markdown';
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { Card } from '~/components/common/Card';
|
||||
import { Markdown } from '~/components/common/Markdown';
|
||||
import { Padder } from '~/components/common/Padder';
|
||||
import { NoteMenu } from '~/components/notes/NoteMenu';
|
||||
import { formatText, getPrettyDate } from '~/utils/dom';
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import { FC, useCallback } from 'react';
|
|||
import { FormikConfig, useFormik } from 'formik';
|
||||
import { Asserts, object, string } from 'yup';
|
||||
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Card } from '~/components/common/Card';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Textarea } from '~/components/input/Textarea';
|
||||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Zone } from '~/components/containers/Zone';
|
||||
import { Card } from '~/components/common/Card';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Zone } from '~/components/common/Zone';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { InputRow } from '~/components/input/InputRow';
|
||||
import { Toggle } from '~/components/input/Toggle';
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Placeholder, PlaceholderProps } from '~/components/placeholders/Placeholder';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import {
|
||||
Placeholder,
|
||||
PlaceholderProps,
|
||||
} from '~/components/placeholders/Placeholder';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
|
@ -14,17 +17,21 @@ const Paragraph: FC<Props> = ({ lines = 3, wordsLimit = 12, ...props }) => {
|
|||
const iters = useMemo(
|
||||
() =>
|
||||
[...new Array(lines)].map(() =>
|
||||
[...new Array(Math.ceil(Math.random() * wordsLimit))].map((_, i) => i)
|
||||
[...new Array(Math.ceil(Math.random() * wordsLimit))].map((_, i) => i),
|
||||
),
|
||||
[lines, wordsLimit]
|
||||
[lines, wordsLimit],
|
||||
);
|
||||
|
||||
return (
|
||||
<Group>
|
||||
{iters.map((words, i) => (
|
||||
<div className={styles.para} key={i}>
|
||||
{words.map(word => (
|
||||
<Placeholder key={word} width={`${Math.round(Math.random() * 120) + 60}px`} active />
|
||||
{words.map((word) => (
|
||||
<Placeholder
|
||||
key={word}
|
||||
width={`${Math.round(Math.random() * 120) + 60}px`}
|
||||
active
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { ERROR_LITERAL } from '~/constants/errors';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { FC } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { ProfileLoader } from '~/containers/profile/ProfileLoader';
|
||||
import { useUser } from '~/hooks/auth/useUser';
|
||||
import markdown from '~/styles/common/markdown.module.scss';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Padder } from '~/components/common/Padder';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { UserSettingsView } from '~/containers/settings/UserSettingsView';
|
||||
import {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, VFC } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { HorizontalMenu } from '~/components/menu/HorizontalMenu';
|
||||
import { useStackContext } from '~/components/sidebar/SidebarStack';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { FC } from 'react';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { useStackContext } from '~/components/sidebar/SidebarStack';
|
||||
import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard';
|
||||
|
|
|
@ -2,8 +2,8 @@ import { VFC } from 'react';
|
|||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Group } from '~/components/common/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { VerticalMenu } from '~/components/menu/VerticalMenu';
|
||||
import { URLS } from '~/constants/urls';
|
||||
|
@ -28,7 +28,9 @@ const SettingsMenu: VFC<SettingsMenuProps> = () => (
|
|||
</Link>
|
||||
|
||||
<Link href={URLS.SETTINGS.TRASH} passHref>
|
||||
<VerticalMenu.Item onClick={console.log}>Удалённые посты</VerticalMenu.Item>
|
||||
<VerticalMenu.Item onClick={console.log}>
|
||||
Удалённые посты
|
||||
</VerticalMenu.Item>
|
||||
</Link>
|
||||
</VerticalMenu>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC, ReactNode, useMemo } from 'react';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
|
|
@ -2,7 +2,7 @@ import { FC } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Filler } from '~/components/common/Filler';
|
||||
import { Container } from '~/containers/main/Container';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
import markdown from '~/styles/common/markdown.module.scss';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue