mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
Merge branch 'master' into develop
# Conflicts: # .env.development
This commit is contained in:
commit
5585d566fd
73 changed files with 1677 additions and 380 deletions
|
@ -1,2 +1,2 @@
|
||||||
REACT_APP_API_HOST: https://pig.staging.vault48.org/
|
REACT_APP_API_HOST: http://localhost:3334/
|
||||||
REACT_APP_REMOTE_CURRENT: https://pig.staging.vault48.org/static/
|
REACT_APP_REMOTE_CURRENT: https://pig.staging.vault48.org/static/
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"ramda": "^0.26.1",
|
"ramda": "^0.26.1",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
|
"react-dropzone": "^11.4.2",
|
||||||
"react-masonry-css": "^1.0.16",
|
"react-masonry-css": "^1.0.16",
|
||||||
"react-popper": "^2.2.3",
|
"react-popper": "^2.2.3",
|
||||||
"react-redux": "^7.2.2",
|
"react-redux": "^7.2.2",
|
||||||
|
@ -69,6 +70,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/throttle-debounce": "^2.1.0",
|
||||||
"@craco/craco": "5.8.0",
|
"@craco/craco": "5.8.0",
|
||||||
"@types/classnames": "^2.2.7",
|
"@types/classnames": "^2.2.7",
|
||||||
"@types/marked": "^1.2.2",
|
"@types/marked": "^1.2.2",
|
||||||
|
|
|
@ -19,6 +19,13 @@ const BorisContacts: FC<Props> = () => (
|
||||||
link="https://t.me/boris48bot"
|
link="https://t.me/boris48bot"
|
||||||
subtitle="телеграм-бот"
|
subtitle="телеграм-бот"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<BorisContactItem
|
||||||
|
icon="github"
|
||||||
|
title="Github"
|
||||||
|
link="https://github.com/muerwre?tab=repositories&q=vault"
|
||||||
|
subtitle="исходники Убежища"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,102 +1,73 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { IBorisState } from '~/redux/boris/reducer';
|
import { IBorisState } from '~/redux/boris/reducer';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
|
||||||
import { sizeOf } from '~/utils/dom';
|
import { sizeOf } from '~/utils/dom';
|
||||||
|
import { StatsRow } from '~/components/common/StatsRow';
|
||||||
|
import { SubTitle } from '~/components/common/SubTitle';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
stats: IBorisState['stats'];
|
stats: IBorisState['stats'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Row: FC<{ isLoading: boolean }> = ({ isLoading, children }) => (
|
|
||||||
<li>
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<Placeholder active={isLoading} loading className={styles.label} />
|
|
||||||
<Placeholder active={isLoading} loading className={styles.value} width="24px" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
|
|
||||||
const BorisStatsBackend: FC<IProps> = ({ stats: { is_loading, backend } }) => {
|
const BorisStatsBackend: FC<IProps> = ({ stats: { is_loading, backend } }) => {
|
||||||
// const is_loading = true;
|
|
||||||
|
|
||||||
if (!backend && !is_loading) {
|
if (!backend && !is_loading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<div className={styles.title}>
|
<SubTitle isLoading={is_loading} className={styles.title}>
|
||||||
<Placeholder active={is_loading} loading>
|
Юнитс
|
||||||
Юнитс
|
</SubTitle>
|
||||||
</Placeholder>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="В сознании">
|
||||||
<span className={styles.label}>В сознании</span>
|
{backend.users.alive}
|
||||||
<span className={styles.value}>{backend.users.alive}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="Криокамера">
|
||||||
<span className={styles.label}>Криокамера</span>
|
{backend.users.total - backend.users.alive}
|
||||||
<span className={styles.value}>{backend.users.total - backend.users.alive}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className={styles.title}>
|
<SubTitle isLoading={is_loading} className={styles.title}>
|
||||||
<Placeholder active={is_loading} loading>
|
Контент
|
||||||
Контент
|
</SubTitle>
|
||||||
</Placeholder>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="Фотографии">
|
||||||
<span className={styles.label}>Фотографии</span>
|
{backend.nodes.images}
|
||||||
<span className={styles.value}>{backend.nodes.images}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="Письма">
|
||||||
<span className={styles.label}>Письма</span>
|
{backend.nodes.texts}
|
||||||
<span className={styles.value}>{backend.nodes.texts}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="Видеозаписи">
|
||||||
<span className={styles.label}>Видеозаписи</span>
|
{backend.nodes.videos}
|
||||||
<span className={styles.value}>{backend.nodes.videos}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="Аудиозаписи">
|
||||||
<span className={styles.label}>Аудиозаписи</span>
|
{backend.nodes.audios}
|
||||||
<span className={styles.value}>{backend.nodes.audios}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="Комментарии">
|
||||||
<span className={styles.label}>Комментарии</span>
|
{backend.comments.total}
|
||||||
<span className={styles.value}>{backend.comments.total}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className={styles.title}>
|
<SubTitle isLoading={is_loading} className={styles.title}>
|
||||||
<Placeholder active={is_loading} loading>
|
Сторедж
|
||||||
Сторедж
|
</SubTitle>
|
||||||
</Placeholder>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="Файлы">
|
||||||
<span className={styles.label}>Файлы</span>
|
{backend.files.count}
|
||||||
<span className={styles.value}>{backend.files.count}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row isLoading={is_loading}>
|
<StatsRow isLoading={is_loading} label="На диске">
|
||||||
<span className={styles.label}>На диске</span>
|
{sizeOf(backend.files.size)}
|
||||||
<span className={styles.value}>{sizeOf(backend.files.size)}</span>
|
</StatsRow>
|
||||||
</Row>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,34 +1,10 @@
|
||||||
@import "src/styles/variables";
|
@import "src/styles/variables";
|
||||||
|
|
||||||
.value {
|
.title {
|
||||||
float: right;
|
margin: $gap * 2 0 $gap;
|
||||||
color: white;
|
|
||||||
font: $font_12_semibold;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
ul {
|
|
||||||
font: $font_12_regular;
|
|
||||||
line-height: 24px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
li {
|
|
||||||
border-bottom: 1px solid #333333;
|
|
||||||
color: #aaaaaa;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font: $font_12_semibold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
opacity: 0.3;
|
|
||||||
margin: $gap * 2 0 $gap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useCommentFormFormik } from '~/utils/hooks/useCommentFormFormik';
|
||||||
import { FormikProvider } from 'formik';
|
import { FormikProvider } from 'formik';
|
||||||
import { LocalCommentFormTextarea } from '~/components/comment/LocalCommentFormTextarea';
|
import { LocalCommentFormTextarea } from '~/components/comment/LocalCommentFormTextarea';
|
||||||
import { Button } from '~/components/input/Button';
|
import { Button } from '~/components/input/Button';
|
||||||
import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/fileUploader';
|
import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/useFileUploader';
|
||||||
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants';
|
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants';
|
||||||
import { CommentFormAttachButtons } from '~/components/comment/CommentFormAttachButtons';
|
import { CommentFormAttachButtons } from '~/components/comment/CommentFormAttachButtons';
|
||||||
import { CommentFormFormatButtons } from '~/components/comment/CommentFormFormatButtons';
|
import { CommentFormFormatButtons } from '~/components/comment/CommentFormFormatButtons';
|
||||||
|
@ -11,7 +11,7 @@ import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches';
|
||||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||||
import { IComment, INode } from '~/redux/types';
|
import { IComment, INode } from '~/redux/types';
|
||||||
import { EMPTY_COMMENT } from '~/redux/node/constants';
|
import { EMPTY_COMMENT } from '~/redux/node/constants';
|
||||||
import { CommentFormDropzone } from '~/components/comment/CommentFormDropzone';
|
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { ERROR_LITERAL } from '~/constants/errors';
|
import { ERROR_LITERAL } from '~/constants/errors';
|
||||||
import { useInputPasteUpload } from '~/utils/hooks/useInputPasteUpload';
|
import { useInputPasteUpload } from '~/utils/hooks/useInputPasteUpload';
|
||||||
|
@ -50,7 +50,7 @@ const CommentForm: FC<IProps> = ({ comment, nodeId, onCancelEdit }) => {
|
||||||
useInputPasteUpload(textarea, uploader.uploadFiles);
|
useInputPasteUpload(textarea, uploader.uploadFiles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommentFormDropzone onUpload={uploader.uploadFiles}>
|
<UploadDropzone onUpload={uploader.uploadFiles}>
|
||||||
<form onSubmit={formik.handleSubmit} className={styles.wrap}>
|
<form onSubmit={formik.handleSubmit} className={styles.wrap}>
|
||||||
<FormikProvider value={formik}>
|
<FormikProvider value={formik}>
|
||||||
<FileUploaderProvider value={uploader}>
|
<FileUploaderProvider value={uploader}>
|
||||||
|
@ -103,7 +103,7 @@ const CommentForm: FC<IProps> = ({ comment, nodeId, onCancelEdit }) => {
|
||||||
</FileUploaderProvider>
|
</FileUploaderProvider>
|
||||||
</FormikProvider>
|
</FormikProvider>
|
||||||
</form>
|
</form>
|
||||||
</CommentFormDropzone>
|
</UploadDropzone>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { SortableAudioGrid } from '~/components/editors/SortableAudioGrid';
|
||||||
import { IFile } from '~/redux/types';
|
import { IFile } from '~/redux/types';
|
||||||
import { SortEnd } from 'react-sortable-hoc';
|
import { SortEnd } from 'react-sortable-hoc';
|
||||||
import { moveArrItem } from '~/utils/fn';
|
import { moveArrItem } from '~/utils/fn';
|
||||||
import { useDropZone } from '~/utils/hooks';
|
import { useFileDropZone } from '~/utils/hooks';
|
||||||
import { COMMENT_FILE_TYPES, UPLOAD_TYPES } from '~/redux/uploads/constants';
|
import { COMMENT_FILE_TYPES, UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||||
import { useFileUploaderContext } from '~/utils/hooks/fileUploader';
|
import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
|
||||||
|
|
||||||
const CommentFormAttaches: FC = () => {
|
const CommentFormAttaches: FC = () => {
|
||||||
const uploader = useFileUploaderContext();
|
const uploader = useFileUploaderContext();
|
||||||
|
@ -29,7 +29,7 @@ const CommentFormAttaches: FC = () => {
|
||||||
pending,
|
pending,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onDrop = useDropZone(uploadFiles, COMMENT_FILE_TYPES);
|
const onDrop = useFileDropZone(uploadFiles, COMMENT_FILE_TYPES);
|
||||||
|
|
||||||
const hasImageAttaches = images.length > 0 || pendingImages.length > 0;
|
const hasImageAttaches = images.length > 0 || pendingImages.length > 0;
|
||||||
const hasAudioAttaches = audios.length > 0 || pendingAudios.length > 0;
|
const hasAudioAttaches = audios.length > 0 || pendingAudios.length > 0;
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import React, { FC } from 'react';
|
|
||||||
import { COMMENT_FILE_TYPES } from '~/redux/uploads/constants';
|
|
||||||
import { useDropZone } from '~/utils/hooks';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
onUpload: (files: File[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommentFormDropzone: FC<IProps> = ({ children, onUpload }) => {
|
|
||||||
const onDrop = useDropZone(onUpload, COMMENT_FILE_TYPES);
|
|
||||||
return <div onDropCapture={onDrop}>{children}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { CommentFormDropzone };
|
|
|
@ -10,11 +10,20 @@ interface Props extends DivProps {
|
||||||
url?: string;
|
url?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
|
preset?: typeof PRESETS[keyof typeof PRESETS];
|
||||||
innerRef?: React.Ref<any>;
|
innerRef?: React.Ref<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Avatar: FC<Props> = ({ url, username, size, className, innerRef, ...rest }) => {
|
const Avatar: FC<Props> = ({
|
||||||
const backgroundImage = !!url ? `url('${getURLFromString(url, PRESETS.avatar)}')` : undefined;
|
url,
|
||||||
|
username,
|
||||||
|
size,
|
||||||
|
className,
|
||||||
|
innerRef,
|
||||||
|
preset = PRESETS.avatar,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const backgroundImage = !!url ? `url('${getURLFromString(url, preset)}')` : undefined;
|
||||||
const onOpenProfile = useCallback(() => openUserProfile(username), [username]);
|
const onOpenProfile = useCallback(() => openUserProfile(username), [username]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
21
src/components/common/StatsRow/index.tsx
Normal file
21
src/components/common/StatsRow/index.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
const StatsRow: FC<{ isLoading: boolean; label: string }> = ({ isLoading, label, children }) => (
|
||||||
|
<li className={styles.row}>
|
||||||
|
{isLoading ? (
|
||||||
|
<>
|
||||||
|
<Placeholder active={isLoading} loading className={styles.label} />
|
||||||
|
<Placeholder active={isLoading} loading className={styles.value} width="24px" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className={styles.label}>{label}</div>
|
||||||
|
<div className={styles.value}>{children}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { StatsRow };
|
23
src/components/common/StatsRow/styles.module.scss
Normal file
23
src/components/common/StatsRow/styles.module.scss
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
@import '~/styles/variables.scss';
|
||||||
|
|
||||||
|
.row {
|
||||||
|
@include row_shadow;
|
||||||
|
color: #aaaaaa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font: $font_12_regular;
|
||||||
|
line-height: 24px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
float: right;
|
||||||
|
color: white;
|
||||||
|
font: $font_12_semibold;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
19
src/components/common/SubTitle/index.tsx
Normal file
19
src/components/common/SubTitle/index.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||||
|
import { DivProps } from '~/utils/types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
interface Props extends DivProps {
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubTitle: FC<Props> = ({ isLoading, children, ...rest }) => (
|
||||||
|
<div {...rest} className={classNames(styles.title, rest.className)}>
|
||||||
|
<Placeholder active={isLoading} loading>
|
||||||
|
{children}
|
||||||
|
</Placeholder>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { SubTitle };
|
7
src/components/common/SubTitle/styles.module.scss
Normal file
7
src/components/common/SubTitle/styles.module.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@import "~/styles/variables.scss";
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font: $font_12_semibold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
|
@ -11,13 +11,14 @@ import { NodeEditorProps } from '~/redux/node/types';
|
||||||
import { useNodeImages } from '~/utils/hooks/node/useNodeImages';
|
import { useNodeImages } from '~/utils/hooks/node/useNodeImages';
|
||||||
import { useNodeAudios } from '~/utils/hooks/node/useNodeAudios';
|
import { useNodeAudios } from '~/utils/hooks/node/useNodeAudios';
|
||||||
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
||||||
import { useFileUploaderContext } from '~/utils/hooks/fileUploader';
|
import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
|
||||||
|
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||||
|
|
||||||
type IProps = NodeEditorProps;
|
type IProps = NodeEditorProps;
|
||||||
|
|
||||||
const AudioEditor: FC<IProps> = () => {
|
const AudioEditor: FC<IProps> = () => {
|
||||||
const { values } = useNodeFormContext();
|
const { values } = useNodeFormContext();
|
||||||
const { pending, setFiles } = useFileUploaderContext()!;
|
const { pending, setFiles, uploadFiles } = useFileUploaderContext()!;
|
||||||
|
|
||||||
const images = useNodeImages(values);
|
const images = useNodeImages(values);
|
||||||
const audios = useNodeAudios(values);
|
const audios = useNodeAudios(values);
|
||||||
|
@ -35,10 +36,12 @@ const AudioEditor: FC<IProps> = () => {
|
||||||
const setAudios = useCallback(values => setFiles([...values, ...images]), [setFiles, images]);
|
const setAudios = useCallback(values => setFiles([...values, ...images]), [setFiles, images]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
|
||||||
<ImageGrid files={images} setFiles={setImages} locked={pendingImages} />
|
<div className={styles.wrap}>
|
||||||
<AudioGrid files={audios} setFiles={setAudios} locked={pendingAudios} />
|
<ImageGrid files={images} setFiles={setImages} locked={pendingImages} />
|
||||||
</div>
|
<AudioGrid files={audios} setFiles={setAudios} locked={pendingAudios} />
|
||||||
|
</div>
|
||||||
|
</UploadDropzone>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,13 @@
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
pointer-events: none;
|
||||||
|
touch-action: none;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
margin: 0 $gap / 2;
|
margin: 0 $gap / 2;
|
||||||
|
pointer-events: all;
|
||||||
|
touch-action: auto;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Filler } from '~/components/containers/Filler';
|
import { Filler } from '~/components/containers/Filler';
|
||||||
import { IEditorComponentProps } from '~/redux/node/types';
|
import { IEditorComponentProps } from '~/redux/node/types';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
type IProps = IEditorComponentProps & {};
|
type IProps = IEditorComponentProps & {};
|
||||||
|
|
||||||
const EditorFiller: FC<IProps> = () => <Filler />;
|
const EditorFiller: FC<IProps> = () => <Filler className={styles.filler} />;
|
||||||
|
|
||||||
export { EditorFiller };
|
export { EditorFiller };
|
||||||
|
|
4
src/components/editors/EditorFiller/styles.module.scss
Normal file
4
src/components/editors/EditorFiller/styles.module.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.filler {
|
||||||
|
touch-action: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import styles from './styles.module.scss';
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||||
import { IEditorComponentProps } from '~/redux/node/types';
|
import { IEditorComponentProps } from '~/redux/node/types';
|
||||||
import { useFileUploaderContext } from '~/utils/hooks/fileUploader';
|
import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
|
||||||
import { getFileType } from '~/utils/uploader';
|
import { getFileType } from '~/utils/uploader';
|
||||||
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
||||||
import { Button } from '~/components/input/Button';
|
import { Button } from '~/components/input/Button';
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { getURL } from '~/utils/dom';
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from '~/constants/urls';
|
||||||
import { IEditorComponentProps } from '~/redux/node/types';
|
import { IEditorComponentProps } from '~/redux/node/types';
|
||||||
import { useFileUploader, useFileUploaderContext } from '~/utils/hooks/fileUploader';
|
import { useFileUploader, useFileUploaderContext } from '~/utils/hooks/useFileUploader';
|
||||||
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
||||||
import { getFileType } from '~/utils/uploader';
|
import { getFileType } from '~/utils/uploader';
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import React, { FC, useMemo, useCallback } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { INode, IFile } from '~/redux/types';
|
|
||||||
import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
|
|
||||||
import { selectUploads } from '~/redux/uploads/selectors';
|
|
||||||
import { ImageGrid } from '~/components/editors/ImageGrid';
|
import { ImageGrid } from '~/components/editors/ImageGrid';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { NodeEditorProps } from '~/redux/node/types';
|
import { NodeEditorProps } from '~/redux/node/types';
|
||||||
import { useFileUploaderContext } from '~/utils/hooks/fileUploader';
|
import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
|
||||||
|
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||||
|
|
||||||
type IProps = NodeEditorProps;
|
type IProps = NodeEditorProps;
|
||||||
|
|
||||||
const ImageEditor: FC<IProps> = () => {
|
const ImageEditor: FC<IProps> = () => {
|
||||||
const { pending, files, setFiles } = useFileUploaderContext()!;
|
const { pending, files, setFiles, uploadFiles } = useFileUploaderContext()!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
|
||||||
<ImageGrid files={files} setFiles={setFiles} locked={pending} />
|
<div className={styles.wrap}>
|
||||||
</div>
|
<ImageGrid files={files} setFiles={setFiles} locked={pending} />
|
||||||
|
</div>
|
||||||
|
</UploadDropzone>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
@import "src/styles/variables";
|
@import 'src/styles/variables';
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
padding-bottom: $upload_button_height + $gap;
|
padding-bottom: $upload_button_height + $gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.dropzone {
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { Icon } from '~/components/input/Icon';
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from '~/constants/urls';
|
||||||
import { NODE_TYPES } from '~/redux/node/constants';
|
import { NODE_TYPES } from '~/redux/node/constants';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { CellShade } from '~/components/flow/CellShade';
|
||||||
|
|
||||||
const THUMBNAIL_SIZES = {
|
const THUMBNAIL_SIZES = {
|
||||||
horizontal: PRESETS.small_hero,
|
horizontal: PRESETS.small_hero,
|
||||||
|
@ -26,7 +27,6 @@ interface IProps {
|
||||||
const Cell: FC<IProps> = ({
|
const Cell: FC<IProps> = ({
|
||||||
node: { id, title, thumbnail, type, flow, description },
|
node: { id, title, thumbnail, type, flow, description },
|
||||||
can_edit,
|
can_edit,
|
||||||
onSelect,
|
|
||||||
onChangeCellView,
|
onChangeCellView,
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
@ -112,6 +112,8 @@ const Cell: FC<IProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Link className={classNames(styles.face)} to={`/post${id}`}>
|
<Link className={classNames(styles.face)} to={`/post${id}`}>
|
||||||
|
<CellShade color={flow.dominant_color} />
|
||||||
|
|
||||||
<div className={styles.face_content}>
|
<div className={styles.face_content}>
|
||||||
{!text && <div className={classNames(styles.title, titleSize)}>{title || '...'}</div>}
|
{!text && <div className={classNames(styles.title, titleSize)}>{title || '...'}</div>}
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,6 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(5deg, transparentize($content_bg, 0), transparentize($content_bg, 1));
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
border-radius: $cell_radius;
|
border-radius: $cell_radius;
|
||||||
padding: $gap / 2;
|
padding: $gap / 2;
|
||||||
|
|
26
src/components/flow/CellShade/index.tsx
Normal file
26
src/components/flow/CellShade/index.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React, { FC, useMemo } from 'react';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { DEFAULT_DOMINANT_COLOR } from '~/constants/node';
|
||||||
|
import { convertHexToRGBA } from '~/utils/color';
|
||||||
|
import { DivProps } from '~/utils/types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
interface Props extends DivProps {
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CellShade: FC<Props> = ({ color, ...rest }) => {
|
||||||
|
const background = useMemo(() => {
|
||||||
|
if (!color || color === DEFAULT_DOMINANT_COLOR) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `linear-gradient(7deg, ${color} 50px, ${convertHexToRGBA(color, 0.3)} 250px)`;
|
||||||
|
}, [color]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...rest} className={classNames(rest.className, styles.shade)} style={{ background }} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CellShade };
|
16
src/components/flow/CellShade/styles.module.scss
Normal file
16
src/components/flow/CellShade/styles.module.scss
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
@import "~/styles/variables";
|
||||||
|
|
||||||
|
.shade {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
background: linear-gradient(7deg, transparentize($content_bg, 0.05) 30px, transparentize($content_bg, 1) 250px);
|
||||||
|
pointer-events: none;
|
||||||
|
touch-action: none;
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
18
src/components/input/DropHereIcon/index.tsx
Normal file
18
src/components/input/DropHereIcon/index.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { SVGProps } from '~/utils/types';
|
||||||
|
|
||||||
|
interface Props extends SVGProps {}
|
||||||
|
|
||||||
|
const DropHereIcon: FC<Props> = ({ ...rest }) => (
|
||||||
|
<svg viewBox="0 0 24 24" stroke="none" {...rest}>
|
||||||
|
<path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z" />
|
||||||
|
|
||||||
|
<path
|
||||||
|
d="M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"
|
||||||
|
className={styles.arrow}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { DropHereIcon };
|
8
src/components/input/DropHereIcon/styles.module.scss
Normal file
8
src/components/input/DropHereIcon/styles.module.scss
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
@keyframes bounce {
|
||||||
|
0% { transform: translate(0, -5%); }
|
||||||
|
100% { transform: translate(0, 5%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
animation: bounce alternate infinite 0.25s;
|
||||||
|
}
|
|
@ -7,9 +7,11 @@ import { IFile } from '~/redux/types';
|
||||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { useResizeHandler } from '~/utils/hooks/useResizeHandler';
|
import { useResizeHandler } from '~/utils/hooks/useResizeHandler';
|
||||||
|
import { DEFAULT_DOMINANT_COLOR } from '~/constants/node';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
file: IFile;
|
file: IFile;
|
||||||
|
color?: string;
|
||||||
onLoad?: () => void;
|
onLoad?: () => void;
|
||||||
onClick?: MouseEventHandler;
|
onClick?: MouseEventHandler;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -18,7 +20,7 @@ interface IProps {
|
||||||
const DEFAULT_WIDTH = 1920;
|
const DEFAULT_WIDTH = 1920;
|
||||||
const DEFAULT_HEIGHT = 1020;
|
const DEFAULT_HEIGHT = 1020;
|
||||||
|
|
||||||
const ImagePreloader: FC<IProps> = ({ file, onLoad, onClick, className }) => {
|
const ImagePreloader: FC<IProps> = ({ file, color, onLoad, onClick, className }) => {
|
||||||
const [maxHeight, setMaxHeight] = useState(window.innerHeight - 140);
|
const [maxHeight, setMaxHeight] = useState(window.innerHeight - 140);
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
|
@ -48,6 +50,7 @@ const ImagePreloader: FC<IProps> = ({ file, onLoad, onClick, className }) => {
|
||||||
useResizeHandler(onResize);
|
useResizeHandler(onResize);
|
||||||
|
|
||||||
const estimatedWidth = (width * maxHeight) / height;
|
const estimatedWidth = (width * maxHeight) / height;
|
||||||
|
const fill = color && color !== DEFAULT_DOMINANT_COLOR ? color : '#222222';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -67,7 +70,7 @@ const ImagePreloader: FC<IProps> = ({ file, onLoad, onClick, className }) => {
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<g filter="url(#f1)">
|
<g filter="url(#f1)">
|
||||||
<rect fill="#222222" width="100%" height="100%" stroke="none" rx="8" ry="8" />
|
<rect fill={fill} width="100%" height="100%" stroke="none" rx="8" ry="8" />
|
||||||
|
|
||||||
{!hasError && (
|
{!hasError && (
|
||||||
<image
|
<image
|
||||||
|
|
|
@ -3,24 +3,24 @@ import { INode } from '~/redux/types';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { Avatar } from '~/components/common/Avatar';
|
import { Avatar } from '~/components/common/Avatar';
|
||||||
import { openUserProfile } from '~/utils/user';
|
import { openUserProfile } from '~/utils/user';
|
||||||
import { useRandomPhrase } from '~/constants/phrases';
|
import { useUserDescription } from '~/utils/hooks/user/useUserDescription';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
node?: INode;
|
node?: INode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeAuthorBlock: FC<Props> = ({ node }) => {
|
const NodeAuthorBlock: FC<Props> = ({ node }) => {
|
||||||
const randomPhrase = useRandomPhrase('USER_DESCRIPTION');
|
|
||||||
|
|
||||||
const onOpenProfile = useCallback(() => openUserProfile(node?.user?.username), [
|
const onOpenProfile = useCallback(() => openUserProfile(node?.user?.username), [
|
||||||
node?.user?.username,
|
node?.user?.username,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const description = useUserDescription(node?.user);
|
||||||
|
|
||||||
if (!node?.user) {
|
if (!node?.user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { fullname, username, description, photo } = node.user;
|
const { fullname, username, photo } = node.user;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.block} onClick={onOpenProfile}>
|
<div className={styles.block} onClick={onOpenProfile}>
|
||||||
|
@ -28,8 +28,7 @@ const NodeAuthorBlock: FC<Props> = ({ node }) => {
|
||||||
|
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<div className={styles.username}>{fullname || username}</div>
|
<div className={styles.username}>{fullname || username}</div>
|
||||||
|
<div className={styles.description}>{description}</div>
|
||||||
<div className={styles.description}>{description || randomPhrase}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -97,6 +97,7 @@ const NodeImageSwiperBlock: FC<IProps> = ({ node }) => {
|
||||||
onLoad={updateSwiper}
|
onLoad={updateSwiper}
|
||||||
onClick={() => onOpenPhotoSwipe(i)}
|
onClick={() => onOpenPhotoSwipe(i)}
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
|
color={file?.metadata?.dominant_color}
|
||||||
/>
|
/>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import styles from './styles.module.scss';
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from '~/components/containers/Group';
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from '~/redux/types';
|
||||||
import { NodeRelatedItem } from '~/components/node/NodeRelatedItem';
|
import { NodeRelatedItem } from '~/components/node/NodeRelatedItem';
|
||||||
|
import { SubTitle } from '~/components/common/SubTitle';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
title: ReactElement | string;
|
title: ReactElement | string;
|
||||||
|
@ -12,9 +13,7 @@ interface IProps {
|
||||||
const NodeRelated: FC<IProps> = ({ title, items }) => {
|
const NodeRelated: FC<IProps> = ({ title, items }) => {
|
||||||
return (
|
return (
|
||||||
<Group className={styles.wrap}>
|
<Group className={styles.wrap}>
|
||||||
<div className={styles.title}>
|
<SubTitle className={styles.title}>{title}</SubTitle>
|
||||||
<div className={styles.text}>{title}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{items.map(item => (
|
{items.map(item => (
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@include title_with_line();
|
padding-left: 5px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
|
@ -17,12 +17,10 @@ const Paragraph: FC<Props> = ({ lines = 3, wordsLimit = 12, ...props }) => {
|
||||||
[lines, wordsLimit]
|
[lines, wordsLimit]
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log({ iters });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
{iters.map(words => (
|
{iters.map((words, i) => (
|
||||||
<div className={styles.para}>
|
<div className={styles.para} key={i}>
|
||||||
{words.map(word => (
|
{words.map(word => (
|
||||||
<Placeholder key={word} width={`${Math.round(Math.random() * 120) + 60}px`} active />
|
<Placeholder key={word} width={`${Math.round(Math.random() * 120) + 60}px`} active />
|
||||||
))}
|
))}
|
||||||
|
|
48
src/components/upload/UploadDropzone/index.tsx
Normal file
48
src/components/upload/UploadDropzone/index.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import React, { FC, useCallback } from 'react';
|
||||||
|
import Dropzone from 'react-dropzone';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { DivProps } from '~/utils/types';
|
||||||
|
import { DropHereIcon } from '~/components/input/DropHereIcon';
|
||||||
|
import { useDragDetector } from '~/utils/hooks/useDragDetector';
|
||||||
|
|
||||||
|
interface IProps extends DivProps {
|
||||||
|
onUpload: (files: File[]) => void;
|
||||||
|
helperClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UploadDropzone: FC<IProps> = ({ children, onUpload, helperClassName, ...rest }) => {
|
||||||
|
const { isDragging: isDraggingOnBody, onStopDragging } = useDragDetector();
|
||||||
|
const onDrop = useCallback(
|
||||||
|
(files: File[]) => {
|
||||||
|
onStopDragging();
|
||||||
|
onUpload(files);
|
||||||
|
},
|
||||||
|
[onUpload]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropzone onDrop={onDrop}>
|
||||||
|
{({ getRootProps, isDragActive }) => (
|
||||||
|
<div
|
||||||
|
{...getRootProps({
|
||||||
|
...rest,
|
||||||
|
className: classnames(styles.zone),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<div
|
||||||
|
className={classNames(styles.helper, helperClassName, {
|
||||||
|
[styles.active]: isDragActive || isDraggingOnBody,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DropHereIcon className={styles.icon} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Dropzone>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { UploadDropzone };
|
34
src/components/upload/UploadDropzone/styles.module.scss
Normal file
34
src/components/upload/UploadDropzone/styles.module.scss
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
@import '~/styles/variables';
|
||||||
|
|
||||||
|
.zone {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: transparentize($wisegreen, 0.7);
|
||||||
|
border-radius: $radius;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: inset $wisegreen 0 0 0 2px;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
touch-action: none;
|
||||||
|
|
||||||
|
&.active, :global(.dragging) & {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.icon {
|
||||||
|
width: auto;
|
||||||
|
height: 72px;
|
||||||
|
fill: $wisegreen;
|
||||||
|
}
|
1
src/constants/node.ts
Normal file
1
src/constants/node.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const DEFAULT_DOMINANT_COLOR = '#000000';
|
1
src/constants/user.ts
Normal file
1
src/constants/user.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const INACTIVE_ACCOUNT_DAYS = 40;
|
|
@ -10,6 +10,7 @@ import { BlurWrapper } from '~/components/containers/BlurWrapper';
|
||||||
import { PageCover } from '~/components/containers/PageCover';
|
import { PageCover } from '~/components/containers/PageCover';
|
||||||
import { BottomContainer } from '~/containers/main/BottomContainer';
|
import { BottomContainer } from '~/containers/main/BottomContainer';
|
||||||
import { MainRouter } from '~/containers/main/MainRouter';
|
import { MainRouter } from '~/containers/main/MainRouter';
|
||||||
|
import { DragDetectorProvider } from '~/utils/hooks/useDragDetector';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
modal: selectModal(state),
|
modal: selectModal(state),
|
||||||
|
@ -21,7 +22,7 @@ type IProps = typeof mapDispatchToProps & ReturnType<typeof mapStateToProps> & {
|
||||||
const Component: FC<IProps> = ({ modal: { is_shown } }) => {
|
const Component: FC<IProps> = ({ modal: { is_shown } }) => {
|
||||||
return (
|
return (
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<div>
|
<DragDetectorProvider>
|
||||||
<BlurWrapper is_blurred={is_shown}>
|
<BlurWrapper is_blurred={is_shown}>
|
||||||
<PageCover />
|
<PageCover />
|
||||||
|
|
||||||
|
@ -32,9 +33,8 @@ const Component: FC<IProps> = ({ modal: { is_shown } }) => {
|
||||||
<MainRouter />
|
<MainRouter />
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
</BlurWrapper>
|
</BlurWrapper>
|
||||||
|
|
||||||
<BottomContainer />
|
<BottomContainer />
|
||||||
</div>
|
</DragDetectorProvider>
|
||||||
</ConnectedRouter>
|
</ConnectedRouter>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
||||||
import { prop } from 'ramda';
|
import { prop } from 'ramda';
|
||||||
import { useNodeFormFormik } from '~/utils/hooks/useNodeFormFormik';
|
import { useNodeFormFormik } from '~/utils/hooks/useNodeFormFormik';
|
||||||
import { EditorButtons } from '~/components/editors/EditorButtons';
|
import { EditorButtons } from '~/components/editors/EditorButtons';
|
||||||
import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/fileUploader';
|
import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/useFileUploader';
|
||||||
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants';
|
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants';
|
||||||
import { FormikProvider } from 'formik';
|
import { FormikProvider } from 'formik';
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from '~/redux/types';
|
||||||
|
@ -15,6 +15,7 @@ import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
|
||||||
import { useTranslatedError } from '~/utils/hooks/useTranslatedError';
|
import { useTranslatedError } from '~/utils/hooks/useTranslatedError';
|
||||||
import { useCloseOnEscape } from '~/utils/hooks';
|
import { useCloseOnEscape } from '~/utils/hooks';
|
||||||
import { EditorConfirmClose } from '~/components/editors/EditorConfirmClose';
|
import { EditorConfirmClose } from '~/components/editors/EditorConfirmClose';
|
||||||
|
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||||
|
|
||||||
interface Props extends IDialogProps {
|
interface Props extends IDialogProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import Masonry from 'react-masonry-css';
|
import Masonry from 'react-masonry-css';
|
||||||
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { LabNode } from '~/components/lab/LabNode';
|
import { LabNode } from '~/components/lab/LabNode';
|
||||||
import { selectLabList, selectLabListNodes } from '~/redux/lab/selectors';
|
|
||||||
import { EMPTY_NODE, NODE_TYPES } from '~/redux/node/constants';
|
import { EMPTY_NODE, NODE_TYPES } from '~/redux/node/constants';
|
||||||
import { values } from 'ramda';
|
import { values } from 'ramda';
|
||||||
|
import { ILabNode } from '~/redux/lab/types';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {
|
||||||
|
isLoading: boolean;
|
||||||
|
nodes: ILabNode[];
|
||||||
|
}
|
||||||
|
|
||||||
const breakpointCols = {
|
const breakpointCols = {
|
||||||
default: 2,
|
default: 2,
|
||||||
|
@ -26,11 +28,8 @@ const LoadingNode = () => (
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const LabGrid: FC<IProps> = () => {
|
const LabGrid: FC<IProps> = ({ isLoading, nodes }) => {
|
||||||
const nodes = useShallowSelect(selectLabListNodes);
|
if (isLoading) {
|
||||||
const { is_loading } = useShallowSelect(selectLabList);
|
|
||||||
|
|
||||||
if (is_loading) {
|
|
||||||
return (
|
return (
|
||||||
<Masonry
|
<Masonry
|
||||||
className={styles.wrap}
|
className={styles.wrap}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import { LabTags } from '~/components/lab/LabTags';
|
import { LabTags } from '~/components/lab/LabTags';
|
||||||
import { LabHeroes } from '~/components/lab/LabHeroes';
|
import { LabHeroes } from '~/components/lab/LabHeroes';
|
||||||
import { FlowRecentItem } from '~/components/flow/FlowRecentItem';
|
import { FlowRecentItem } from '~/components/flow/FlowRecentItem';
|
||||||
|
import { SubTitle } from '~/components/common/SubTitle';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
@ -31,10 +32,10 @@ const LabStats: FC<IProps> = () => {
|
||||||
|
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<Group>
|
<Group>
|
||||||
{isLoading ? (
|
{(!!tags.length || isLoading) && (
|
||||||
<Placeholder height={14} width="100px" />
|
<SubTitle isLoading={isLoading} className={styles.title}>
|
||||||
) : (
|
Тэги
|
||||||
tags.length && <div className={styles.title}>Тэги</div>
|
</SubTitle>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.tags}>
|
<div className={styles.tags}>
|
||||||
|
@ -56,10 +57,10 @@ const LabStats: FC<IProps> = () => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isLoading ? (
|
{(!!heroes.length || isLoading) && (
|
||||||
<Placeholder height={14} width="100px" />
|
<SubTitle isLoading={isLoading} className={styles.title}>
|
||||||
) : (
|
Важные
|
||||||
heroes.length > 0 && <div className={styles.title}>Важные</div>
|
</SubTitle>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.heroes}>
|
<div className={styles.heroes}>
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
@import "~/styles/variables.scss";
|
@import "~/styles/variables.scss";
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font: $font_14_semibold;
|
padding-bottom: $gap;
|
||||||
color: darken(white, 50%);
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: 0 $gap / 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags.tags {
|
.tags.tags {
|
||||||
|
@ -26,3 +23,4 @@
|
||||||
.updates {
|
.updates {
|
||||||
padding: 0 $gap / 4 $gap * 2;
|
padding: 0 $gap / 4 $gap * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import markdown from '~/styles/common/markdown.module.scss';
|
import markdown from '~/styles/common/markdown.module.scss';
|
||||||
|
import { ProfileAvatar } from '~/containers/profile/ProfileAvatar';
|
||||||
|
import { Avatar } from '~/components/common/Avatar';
|
||||||
|
import { Markdown } from '~/components/containers/Markdown';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
profile: IAuthState['profile'];
|
profile: IAuthState['profile'];
|
||||||
|
@ -16,49 +19,30 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfilePageLeft: FC<IProps> = ({ username, profile }) => {
|
const ProfilePageLeft: FC<IProps> = ({ username, profile }) => {
|
||||||
const thumb = useMemo(() => {
|
|
||||||
if (!profile || !profile.user || !profile.user.photo) return '';
|
|
||||||
|
|
||||||
return getURL(profile.user.photo, PRESETS.small_hero);
|
|
||||||
}, [profile]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<div className={styles.avatar} style={{ backgroundImage: `url('${thumb}')` }} />
|
<Avatar
|
||||||
|
username={username}
|
||||||
|
url={profile.user?.photo?.url}
|
||||||
|
className={styles.avatar}
|
||||||
|
preset={PRESETS['600']}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className={styles.region_wrap}>
|
<div className={styles.region}>
|
||||||
<div className={styles.region}>
|
<div className={styles.name}>
|
||||||
<div className={styles.name}>
|
{profile.is_loading ? <Placeholder /> : profile?.user?.fullname}
|
||||||
{profile.is_loading ? <Placeholder /> : profile?.user?.fullname}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.username}>
|
<div className={styles.username}>
|
||||||
{profile.is_loading ? <Placeholder /> : `~${profile?.user?.username}`}
|
{profile.is_loading ? <Placeholder /> : `~${profile?.user?.username}`}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.menu}>
|
|
||||||
<Link to={`${URLS.PROFILE_PAGE(username)}/`}>
|
|
||||||
<Icon icon="profile" size={20} />
|
|
||||||
Профиль
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to={`${URLS.PROFILE_PAGE(username)}/settings`}>
|
|
||||||
<Icon icon="settings" size={20} />
|
|
||||||
Настройки
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to={`${URLS.PROFILE_PAGE(username)}/messages`}>
|
|
||||||
<Icon icon="messages" size={20} />
|
|
||||||
Сообщения
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{profile && profile.user && profile.user.description && false && (
|
{profile && profile.user && profile.user.description && (
|
||||||
<div className={classNames(styles.description, markdown.wrapper)}>
|
<Markdown
|
||||||
{formatText(profile?.user?.description || '')}
|
className={styles.description}
|
||||||
</div>
|
dangerouslySetInnerHTML={{ __html: formatText(profile.user.description) }}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,92 +1,51 @@
|
||||||
@import "src/styles/variables";
|
@import "src/styles/variables";
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
|
@include outer_shadow;
|
||||||
|
|
||||||
|
padding: $gap $gap $gap * 2;
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
background: $comment_bg;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: $radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 75%;
|
height: 0;
|
||||||
border-radius: 0 $radius 0 0;
|
padding-bottom: 100%;
|
||||||
background: 50% 50% no-repeat;
|
margin-bottom: $gap * 2;
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.region_wrap {
|
|
||||||
width: 100%;
|
|
||||||
// padding: 0 10px;
|
|
||||||
position: relative;
|
|
||||||
margin-top: -$radius;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.region {
|
.region {
|
||||||
// background: $content_bg;
|
|
||||||
background: darken($content_bg, 2%);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 0 $radius $radius 0;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
font: $font_24_semibold;
|
font: $font_24_semibold;
|
||||||
color: white;
|
color: white;
|
||||||
padding: $gap $gap 0 $gap;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.username {
|
.username {
|
||||||
font: $font_14_semibold;
|
font: $font_14_regular;
|
||||||
padding: 0 $gap $gap $gap;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: transparentize(white, 0.5);
|
color: transparentize(white, 0.5);
|
||||||
margin-top: $gap / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
padding: $gap 0 $gap 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
width: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
a {
|
|
||||||
width: 100%;
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font: $font_18_semibold;
|
|
||||||
padding: $gap $gap;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
opacity: 0.5;
|
|
||||||
box-sizing: border-box;
|
|
||||||
transition: opacity 0.25s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
margin-right: $gap;
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
padding: $gap;
|
@include clamp(3, 21px * 3);
|
||||||
box-sizing: border-box;
|
line-height: 21px;
|
||||||
// background: darken($content_bg, 2%);
|
font: $font_14_regular;
|
||||||
background: darken($content_bg, 4%);
|
margin-top: $gap * 3;
|
||||||
// margin: 0 $gap;
|
display: none;
|
||||||
border-radius: 0 0 $radius $radius;
|
|
||||||
}
|
}
|
||||||
|
|
36
src/containers/profile/ProfilePageStats/index.tsx
Normal file
36
src/containers/profile/ProfilePageStats/index.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { StatsRow } from '~/components/common/StatsRow';
|
||||||
|
import { SubTitle } from '~/components/common/SubTitle';
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
const Row: FC<{ count: number; title: string; icon?: string }> = ({ count, title, icon }) => (
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={styles.title}>{title}</div>
|
||||||
|
<div className={styles.counter}>{count > 999 ? '999+' : count}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ProfilePageStats: FC<Props> = () => (
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
<SubTitle>Ачивментс</SubTitle>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<StatsRow isLoading={false} label="лет в бункере">
|
||||||
|
9
|
||||||
|
</StatsRow>
|
||||||
|
<StatsRow isLoading={false} label="постов">
|
||||||
|
99
|
||||||
|
</StatsRow>
|
||||||
|
<StatsRow isLoading={false} label="комментариев">
|
||||||
|
999+
|
||||||
|
</StatsRow>
|
||||||
|
<StatsRow isLoading={false} label="лайков">
|
||||||
|
99
|
||||||
|
</StatsRow>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { ProfilePageStats };
|
43
src/containers/profile/ProfilePageStats/styles.module.scss
Normal file
43
src/containers/profile/ProfilePageStats/styles.module.scss
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
@import "~/styles/variables";
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
@include outer_shadow;
|
||||||
|
|
||||||
|
padding: $gap;
|
||||||
|
background: $content_bg;
|
||||||
|
border-radius: $radius;
|
||||||
|
display: grid;
|
||||||
|
grid-column-gap: $gap;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-row-gap: $gap;
|
||||||
|
|
||||||
|
& > .row:not(:last-child) {
|
||||||
|
margin-bottom: $gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: $radius;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
font: $font_16_semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font: $font_12_semibold;
|
||||||
|
text-align: left;
|
||||||
|
word-wrap: break-word;
|
||||||
|
opacity: 0.5;
|
||||||
|
flex: 1;
|
||||||
|
}
|
|
@ -55,10 +55,6 @@ const FlowLayout: FC = () => {
|
||||||
labUpdates,
|
labUpdates,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.scrollTo(0, (window as any).flowScrollPos || 0);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.container, { [styles.fluid]: isFluid })}>
|
<div className={classNames(styles.container, { [styles.fluid]: isFluid })}>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
|
|
|
@ -9,66 +9,15 @@
|
||||||
|
|
||||||
$cols: $content_width / $cell;
|
$cols: $content_width / $cell;
|
||||||
|
|
||||||
@mixin fluid {
|
|
||||||
@media(min-width: $content_width) {
|
|
||||||
.fluid & {
|
|
||||||
@content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: $content_width;
|
max-width: $content_width;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&.fluid {
|
|
||||||
padding: 0 $gap;
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax($cell - 5, 1fr));
|
|
||||||
grid-template-rows: 50vh $cell;
|
grid-template-rows: 50vh $cell;
|
||||||
grid-auto-rows: $cell;
|
|
||||||
|
|
||||||
grid-auto-flow: row dense;
|
@include flow_grid;
|
||||||
grid-column-gap: $gap;
|
|
||||||
grid-row-gap: $gap;
|
|
||||||
|
|
||||||
@include fluid {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax($fluid_cell - 5, 1fr));
|
|
||||||
grid-template-rows: $fluid_cell;
|
|
||||||
grid-auto-rows: $fluid_cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: ($cell + 10) * 3) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax($fluid_cell - 20, 1fr));
|
|
||||||
grid-auto-rows: $fluid_cell;
|
|
||||||
grid-template-rows: calc(50vw - 10px) $fluid_cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $cell_tablet) {
|
|
||||||
grid-template-rows: calc(66vw - 10px) auto $fluid_cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $cell_mobile) {
|
|
||||||
// rework stamp, so it will be shown as smaller one on mobiles
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(50vw - 20px), 1fr));
|
|
||||||
grid-template-rows: calc(80vw - 10px) auto 50vw;
|
|
||||||
grid-auto-rows: 50vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: ($fluid_cell + 5) * 1.5 + 20) {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(calc(50vw - 20px), 1fr));
|
|
||||||
grid-template-rows: calc(80vw - 10px) auto 50vw;
|
|
||||||
grid-auto-rows: 50vw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pad_last {
|
.pad_last {
|
||||||
|
@ -86,15 +35,6 @@ $cols: $content_width / $cell;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font: $font_24_semibold;
|
font: $font_24_semibold;
|
||||||
|
|
||||||
@include fluid {
|
|
||||||
grid-row-end: span 2;
|
|
||||||
grid-column-end: span 4;
|
|
||||||
|
|
||||||
@media(max-width: $content_width) {
|
|
||||||
grid-column-end: -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stamp {
|
.stamp {
|
||||||
|
|
|
@ -15,28 +15,33 @@ import { Superpower } from '~/components/boris/Superpower';
|
||||||
import { Toggle } from '~/components/input/Toggle';
|
import { Toggle } from '~/components/input/Toggle';
|
||||||
import { usePersistedState } from '~/utils/hooks/usePersistedState';
|
import { usePersistedState } from '~/utils/hooks/usePersistedState';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useLabPagination } from '~/utils/hooks/lab/useLabPagination';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
const LabLayout: FC<IProps> = () => {
|
const LabLayout: FC<IProps> = () => {
|
||||||
const { is_loading } = useShallowSelect(selectLabList);
|
const { is_loading, nodes } = useShallowSelect(selectLabList);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useLabPagination({ isLoading: is_loading });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(labGetList());
|
dispatch(labGetList());
|
||||||
dispatch(labGetStats());
|
dispatch(labGetStats());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const isInitialLoading = is_loading && !nodes.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<Group className={styles.content}>
|
<Group className={styles.content}>
|
||||||
<div className={styles.head}>
|
<div className={styles.head}>
|
||||||
<LabHead isLoading={is_loading} />
|
<LabHead isLoading={isInitialLoading} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LabGrid />
|
<LabGrid nodes={nodes} isLoading={isInitialLoading} />
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<div className={styles.panel}>
|
<div className={styles.panel}>
|
||||||
|
|
|
@ -50,7 +50,7 @@ const NodeLayout: FC<IProps> = memo(
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
{head}
|
{head}
|
||||||
|
|
||||||
<Container>
|
<Container className={styles.content}>
|
||||||
<Card className={styles.node} seamless>
|
<Card className={styles.node} seamless>
|
||||||
{block}
|
{block}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
align-items: stretch !important;
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
@include vertical_at_tablet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments {
|
.comments {
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import React, { FC, useEffect } from 'react';
|
import React, { FC, useEffect } from 'react';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { Route, RouteComponentProps, Switch } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { authLoadProfile } from '~/redux/auth/actions';
|
import { authLoadProfile } from '~/redux/auth/actions';
|
||||||
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||||
import { selectAuthProfile } from '~/redux/auth/selectors';
|
import { selectAuthProfile, selectUser } from '~/redux/auth/selectors';
|
||||||
import { ProfilePageLeft } from '~/containers/profile/ProfilePageLeft';
|
import { ProfilePageLeft } from '~/containers/profile/ProfilePageLeft';
|
||||||
import { Container } from '~/containers/main/Container';
|
import { Container } from '~/containers/main/Container';
|
||||||
|
import { FlowGrid } from '~/components/flow/FlowGrid';
|
||||||
|
import { Sticky } from '~/components/containers/Sticky';
|
||||||
|
import { selectFlow } from '~/redux/flow/selectors';
|
||||||
|
import { ProfilePageStats } from '~/containers/profile/ProfilePageStats';
|
||||||
|
import { Card } from '~/components/containers/Card';
|
||||||
|
|
||||||
type Props = RouteComponentProps<{ username: string }> & {};
|
type Props = RouteComponentProps<{ username: string }> & {};
|
||||||
|
|
||||||
|
@ -15,6 +20,9 @@ const ProfileLayout: FC<Props> = ({
|
||||||
params: { username },
|
params: { username },
|
||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
|
const { nodes } = useShallowSelect(selectFlow);
|
||||||
|
const user = useShallowSelect(selectUser);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -25,7 +33,27 @@ const ProfileLayout: FC<Props> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={styles.wrap}>
|
<Container className={styles.wrap}>
|
||||||
<ProfilePageLeft profile={profile} username={username} />
|
<div className={styles.left}>
|
||||||
|
<Sticky>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<ProfilePageLeft profile={profile} username={username} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!!profile.user?.description && (
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Card className={styles.description}>{profile.user.description}</Card>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.row}>
|
||||||
|
<ProfilePageStats />
|
||||||
|
</div>
|
||||||
|
</Sticky>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.grid}>
|
||||||
|
<FlowGrid nodes={nodes} user={user} onChangeCellView={console.log} />
|
||||||
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
@import "src/styles/variables";
|
@import "src/styles/variables";
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
flex: 1;
|
display: grid;
|
||||||
display: flex;
|
grid-template-columns: $cell auto;
|
||||||
align-items: stretch;
|
grid-column-gap: $gap;
|
||||||
justify-content: stretch;
|
|
||||||
border-radius: $radius;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
@include flow_grid;
|
||||||
|
}
|
||||||
.left {
|
.left {
|
||||||
flex: 1;
|
|
||||||
background: darken($content_bg, 2%);
|
|
||||||
border-radius: 0 $radius $radius 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.row {
|
||||||
flex: 4;
|
margin-bottom: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font: $font_14_semibold;
|
||||||
|
text-align: center;
|
||||||
|
padding: $gap * 2 $gap;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,3 +34,7 @@ export const labSeenNode = (nodeId: INode['id']) => ({
|
||||||
type: LAB_ACTIONS.SEEN_NODE,
|
type: LAB_ACTIONS.SEEN_NODE,
|
||||||
nodeId,
|
nodeId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const labGetMore = () => ({
|
||||||
|
type: LAB_ACTIONS.GET_MORE,
|
||||||
|
});
|
||||||
|
|
|
@ -10,4 +10,5 @@ export const LAB_ACTIONS = {
|
||||||
SET_UPDATES: `${prefix}SET_UPDATES`,
|
SET_UPDATES: `${prefix}SET_UPDATES`,
|
||||||
GET_UPDATES: `${prefix}GET_UPDATES`,
|
GET_UPDATES: `${prefix}GET_UPDATES`,
|
||||||
SEEN_NODE: `${prefix}SET_UPDATE_SEEN`,
|
SEEN_NODE: `${prefix}SET_UPDATE_SEEN`,
|
||||||
|
GET_MORE: `${prefix}GET_MORE`,
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import { LAB_ACTIONS } from '~/redux/lab/constants';
|
import { LAB_ACTIONS } from '~/redux/lab/constants';
|
||||||
import { Unwrap } from '~/redux/types';
|
import { Unwrap } from '~/redux/types';
|
||||||
import { getLabNodes, getLabStats, getLabUpdates } from '~/redux/lab/api';
|
import { getLabNodes, getLabStats, getLabUpdates } from '~/redux/lab/api';
|
||||||
import { selectLabUpdatesNodes } from '~/redux/lab/selectors';
|
import { selectLabList, selectLabUpdatesNodes } from '~/redux/lab/selectors';
|
||||||
|
|
||||||
function* getList({ after = '' }: ReturnType<typeof labGetList>) {
|
function* getList({ after = '' }: ReturnType<typeof labGetList>) {
|
||||||
try {
|
try {
|
||||||
|
@ -53,10 +53,38 @@ function* seenNode({ nodeId }: ReturnType<typeof labSeenNode>) {
|
||||||
yield put(labSetUpdates({ nodes: newNodes }));
|
yield put(labSetUpdates({ nodes: newNodes }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* getMore() {
|
||||||
|
try {
|
||||||
|
yield put(labSetList({ is_loading: true }));
|
||||||
|
|
||||||
|
const list: ReturnType<typeof selectLabList> = yield select(selectLabList);
|
||||||
|
if (list.nodes.length === list.count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const last = list.nodes[list.nodes.length - 1];
|
||||||
|
|
||||||
|
if (!last) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const after = last.node.commented_at || last.node.created_at;
|
||||||
|
const { nodes, count }: Unwrap<typeof getLabNodes> = yield call(getLabNodes, { after });
|
||||||
|
const newNodes = [...list.nodes, ...nodes];
|
||||||
|
|
||||||
|
yield put(labSetList({ nodes: newNodes, count }));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(labSetList({ error: error.message }));
|
||||||
|
} finally {
|
||||||
|
yield put(labSetList({ is_loading: false }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function* labSaga() {
|
export default function* labSaga() {
|
||||||
yield takeLeading(LAB_ACTIONS.GET_LIST, getList);
|
yield takeLeading(LAB_ACTIONS.GET_LIST, getList);
|
||||||
yield takeLeading(LAB_ACTIONS.GET_STATS, getStats);
|
yield takeLeading(LAB_ACTIONS.GET_STATS, getStats);
|
||||||
|
|
||||||
yield takeLeading(LAB_ACTIONS.GET_UPDATES, getUpdates);
|
yield takeLeading(LAB_ACTIONS.GET_UPDATES, getUpdates);
|
||||||
yield takeLeading(LAB_ACTIONS.SEEN_NODE, seenNode);
|
yield takeLeading(LAB_ACTIONS.SEEN_NODE, seenNode);
|
||||||
|
|
||||||
|
yield takeLeading(LAB_ACTIONS.GET_MORE, getMore);
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ export interface IFile {
|
||||||
duration?: number;
|
duration?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
dominant_color?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
|
@ -133,6 +134,7 @@ export interface INode {
|
||||||
flow: {
|
flow: {
|
||||||
display: 'single' | 'vertical' | 'horizontal' | 'quadro';
|
display: 'single' | 'vertical' | 'horizontal' | 'quadro';
|
||||||
show_description: boolean;
|
show_description: boolean;
|
||||||
|
dominant_color?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
tags: ITag[];
|
tags: ITag[];
|
||||||
|
|
|
@ -74,3 +74,5 @@ export const COMMENT_FILE_TYPES = [
|
||||||
...FILE_MIMES[UPLOAD_TYPES.IMAGE],
|
...FILE_MIMES[UPLOAD_TYPES.IMAGE],
|
||||||
...FILE_MIMES[UPLOAD_TYPES.AUDIO],
|
...FILE_MIMES[UPLOAD_TYPES.AUDIO],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const IMAGE_FILE_TYPES = [...FILE_MIMES[UPLOAD_TYPES.IMAGE]];
|
||||||
|
|
|
@ -325,6 +325,23 @@ const Sprites: FC = () => (
|
||||||
d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10c5.52,0,10-4.48,10-10C22,6.48,17.52,2,12,2z M16.64,8.8 c-0.15,1.58-0.8,5.42-1.13,7.19c-0.14,0.75-0.42,1-0.68,1.03c-0.58,0.05-1.02-0.38-1.58-0.75c-0.88-0.58-1.38-0.94-2.23-1.5 c-0.99-0.65-0.35-1.01,0.22-1.59c0.15-0.15,2.71-2.48,2.76-2.69c0.01-0.03,0.01-0.12-0.05-0.18c-0.06-0.05-0.14-0.03-0.21-0.02 c-0.09,0.02-1.49,0.95-4.22,2.79c-0.4,0.27-0.76,0.41-1.08,0.4c-0.36-0.01-1.04-0.2-1.55-0.37c-0.63-0.2-1.12-0.31-1.08-0.66 c0.02-0.18,0.27-0.36,0.74-0.55c2.92-1.27,4.86-2.11,5.83-2.51c2.78-1.16,3.35-1.36,3.73-1.36c0.08,0,0.27,0.02,0.39,0.12 c0.1,0.08,0.13,0.19,0.14,0.27C16.63,8.48,16.65,8.66,16.64,8.8z"
|
d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10c5.52,0,10-4.48,10-10C22,6.48,17.52,2,12,2z M16.64,8.8 c-0.15,1.58-0.8,5.42-1.13,7.19c-0.14,0.75-0.42,1-0.68,1.03c-0.58,0.05-1.02-0.38-1.58-0.75c-0.88-0.58-1.38-0.94-2.23-1.5 c-0.99-0.65-0.35-1.01,0.22-1.59c0.15-0.15,2.71-2.48,2.76-2.69c0.01-0.03,0.01-0.12-0.05-0.18c-0.06-0.05-0.14-0.03-0.21-0.02 c-0.09,0.02-1.49,0.95-4.22,2.79c-0.4,0.27-0.76,0.41-1.08,0.4c-0.36-0.01-1.04-0.2-1.55-0.37c-0.63-0.2-1.12-0.31-1.08-0.66 c0.02-0.18,0.27-0.36,0.74-0.55c2.92-1.27,4.86-2.11,5.83-2.51c2.78-1.16,3.35-1.36,3.73-1.36c0.08,0,0.27,0.02,0.39,0.12 c0.1,0.08,0.13,0.19,0.14,0.27C16.63,8.48,16.65,8.66,16.64,8.8z"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
<g id="github">
|
||||||
|
<path fill="none" d="M0 0h24v24H0V0z" stroke="none" />
|
||||||
|
<path
|
||||||
|
transform="scale(0.05)"
|
||||||
|
stroke="none"
|
||||||
|
d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365 c-39.781,0-76.472,9.804-110.063,29.408c-33.596,19.605-60.192,46.204-79.8,79.8C9.803,148.168,0,184.854,0,224.63 c0,47.78,13.94,90.745,41.827,128.906c27.884,38.164,63.906,64.572,108.063,79.227c5.14,0.954,8.945,0.283,11.419-1.996 c2.475-2.282,3.711-5.14,3.711-8.562c0-0.571-0.049-5.708-0.144-15.417c-0.098-9.709-0.144-18.179-0.144-25.406l-6.567,1.136 c-4.187,0.767-9.469,1.092-15.846,1c-6.374-0.089-12.991-0.757-19.842-1.999c-6.854-1.231-13.229-4.086-19.13-8.559 c-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559 c-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-0.951-2.568-2.098-3.711-3.429c-1.142-1.331-1.997-2.663-2.568-3.997 c-0.572-1.335-0.098-2.43,1.427-3.289c1.525-0.859,4.281-1.276,8.28-1.276l5.708,0.853c3.807,0.763,8.516,3.042,14.133,6.851 c5.614,3.806,10.229,8.754,13.846,14.842c4.38,7.806,9.657,13.754,15.846,17.847c6.184,4.093,12.419,6.136,18.699,6.136 c6.28,0,11.704-0.476,16.274-1.423c4.565-0.952,8.848-2.383,12.847-4.285c1.713-12.758,6.377-22.559,13.988-29.41 c-10.848-1.14-20.601-2.857-29.264-5.14c-8.658-2.286-17.605-5.996-26.835-11.14c-9.235-5.137-16.896-11.516-22.985-19.126 c-6.09-7.614-11.088-17.61-14.987-29.979c-3.901-12.374-5.852-26.648-5.852-42.826c0-23.035,7.52-42.637,22.557-58.817 c-7.044-17.318-6.379-36.732,1.997-58.24c5.52-1.715,13.706-0.428,24.554,3.853c10.85,4.283,18.794,7.952,23.84,10.994 c5.046,3.041,9.089,5.618,12.135,7.708c17.705-4.947,35.976-7.421,54.818-7.421s37.117,2.474,54.823,7.421l10.849-6.849 c7.419-4.57,16.18-8.758,26.262-12.565c10.088-3.805,17.802-4.853,23.134-3.138c8.562,21.509,9.325,40.922,2.279,58.24 c15.036,16.18,22.559,35.787,22.559,58.817c0,16.178-1.958,30.497-5.853,42.966c-3.9,12.471-8.941,22.457-15.125,29.979 c-6.191,7.521-13.901,13.85-23.131,18.986c-9.232,5.14-18.182,8.85-26.84,11.136c-8.662,2.286-18.415,4.004-29.263,5.146 c9.894,8.562,14.842,22.077,14.842,40.539v60.237c0,3.422,1.19,6.279,3.572,8.562c2.379,2.279,6.136,2.95,11.276,1.995 c44.163-14.653,80.185-41.062,108.068-79.226c27.88-38.161,41.825-81.126,41.825-128.906 C438.536,184.851,428.728,148.168,409.132,114.573z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="upload">
|
||||||
|
<path fill="none" d="M0 0h24v24H0V0z" stroke="none" />
|
||||||
|
<path
|
||||||
|
stroke="none"
|
||||||
|
d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
883
src/sprites/boris_lab.svg
Normal file
883
src/sprites/boris_lab.svg
Normal file
|
@ -0,0 +1,883 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="300"
|
||||||
|
height="300"
|
||||||
|
viewBox="0 0 79.374998 79.375002"
|
||||||
|
version="1.1"
|
||||||
|
id="svg588"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
sodipodi:docname="boris_lab.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview590"
|
||||||
|
pagecolor="#000000"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
showborder="true"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
borderlayer="true"
|
||||||
|
inkscape:zoom="0.35723204"
|
||||||
|
inkscape:cx="-284.12905"
|
||||||
|
inkscape:cy="565.4588"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs585">
|
||||||
|
<radialGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient900"
|
||||||
|
id="radialGradient4635"
|
||||||
|
cx="312.92648"
|
||||||
|
cy="83.708092"
|
||||||
|
fx="312.92648"
|
||||||
|
fy="83.708092"
|
||||||
|
r="44.979172"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(1.7352932,-0.01764706,0.01387765,1.3646349,-170.32084,-62.042332)" />
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient900"
|
||||||
|
inkscape:collect="always">
|
||||||
|
<stop
|
||||||
|
id="stop896"
|
||||||
|
offset="0"
|
||||||
|
style="stop-color:#37483e;stop-opacity:1" />
|
||||||
|
<stop
|
||||||
|
id="stop898"
|
||||||
|
offset="1"
|
||||||
|
style="stop-color:#15241f;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath4984">
|
||||||
|
<circle
|
||||||
|
r="44.979172"
|
||||||
|
cy="117.83934"
|
||||||
|
cx="340.17853"
|
||||||
|
id="circle4986"
|
||||||
|
style="fill:url(#radialGradient4988);fill-opacity:1;stroke-width:0.171116" />
|
||||||
|
</clipPath>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter4955"
|
||||||
|
x="-0.30899032"
|
||||||
|
width="1.6179806"
|
||||||
|
y="-0.76629603"
|
||||||
|
height="2.5325921">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="5.9735542"
|
||||||
|
id="feGaussianBlur4957" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter3376"
|
||||||
|
x="-0.20596256"
|
||||||
|
width="1.4119251"
|
||||||
|
y="-0.51078719"
|
||||||
|
height="2.0215744">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="3.9817705"
|
||||||
|
id="feGaussianBlur3378" />
|
||||||
|
</filter>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient3605"
|
||||||
|
id="linearGradient3607"
|
||||||
|
x1="1218.871"
|
||||||
|
y1="533.89893"
|
||||||
|
x2="1482.9836"
|
||||||
|
y2="503.79688"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient3605">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#00ccff;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop3601" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop3603" />
|
||||||
|
</linearGradient>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter5875"
|
||||||
|
x="-0.11630558"
|
||||||
|
width="1.2326112"
|
||||||
|
y="-0.26428985"
|
||||||
|
height="1.5324787">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="14.972889"
|
||||||
|
id="feGaussianBlur5877" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter3156"
|
||||||
|
x="-0.15793878"
|
||||||
|
width="1.3158776"
|
||||||
|
y="-0.063027151"
|
||||||
|
height="1.1260543">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="0.99741967"
|
||||||
|
id="feGaussianBlur3158" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter3775"
|
||||||
|
x="-0.54122727"
|
||||||
|
width="2.0824545"
|
||||||
|
y="-0.51769563"
|
||||||
|
height="2.0353913">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="5.2506562"
|
||||||
|
id="feGaussianBlur3777" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter3822"
|
||||||
|
x="-0.70077273"
|
||||||
|
width="2.4015455"
|
||||||
|
y="-0.67030432"
|
||||||
|
height="2.3406086">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="6.7984687"
|
||||||
|
id="feGaussianBlur3824" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter3834"
|
||||||
|
x="-0.67990912"
|
||||||
|
width="2.3598182"
|
||||||
|
y="-0.65034783"
|
||||||
|
height="2.3006957">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="6.5960625"
|
||||||
|
id="feGaussianBlur3836" />
|
||||||
|
</filter>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient2008"
|
||||||
|
id="linearGradient2554"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="207.37267"
|
||||||
|
y1="85.704292"
|
||||||
|
x2="207.37267"
|
||||||
|
y2="35.489822" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient2008">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#191e1a;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop2004" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#1a2d2a;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop2006" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient4677"
|
||||||
|
id="linearGradient2556"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="744.84583"
|
||||||
|
y1="-147.4702"
|
||||||
|
x2="689.37933"
|
||||||
|
y2="-319.99997" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient4677">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#1c241f;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop4673" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#2b3931;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop4675" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient2118"
|
||||||
|
id="radialGradient2558"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(4.1025138,4.904937e-7,-4.7336306e-7,3.9592323,-527.377,-192.81817)"
|
||||||
|
cx="168.7189"
|
||||||
|
cy="57.244839"
|
||||||
|
fx="168.7189"
|
||||||
|
fy="57.244839"
|
||||||
|
r="7.9375" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient2118">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#00ffcc;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop2114" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#237d7f;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop2116" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient5666"
|
||||||
|
id="linearGradient2560"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="169.72586"
|
||||||
|
y1="66.1511"
|
||||||
|
x2="191.85043"
|
||||||
|
y2="66.1511" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient5666">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#242f28;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5662" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#1c241f;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop5664" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient5364"
|
||||||
|
id="radialGradient2562"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(2.6948297,0.68882556,-1.8240762,7.1361678,-1588.0171,1034.8652)"
|
||||||
|
cx="672.15662"
|
||||||
|
cy="-246.05667"
|
||||||
|
fx="672.15662"
|
||||||
|
fy="-246.05667"
|
||||||
|
r="13.702145" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient5364">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#536c5d;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5360" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#536c5d;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop5362" />
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath5332">
|
||||||
|
<path
|
||||||
|
style="fill:url(#linearGradient5326);fill-opacity:1;stroke-width:0.264583"
|
||||||
|
d="m 175.41875,43.00005 v 0.454753 a 7.8052081,22.886458 0 0 1 0.92604,-0.190169 7.8052081,22.886458 0 0 1 7.80521,22.886457 7.8052081,22.886458 0 0 1 -7.4967,22.854422 h 26.98956 a 7.8052081,22.886458 0 0 0 0.2186,0.03204 7.8052081,22.886458 0 0 0 7.8052,-22.886461 7.8052081,22.886458 0 0 0 -7.8052,-22.886457 7.8052081,22.886458 0 0 0 -0.1323,0.01394 V 43.00005 Z m 0,45.858186 v 0.147277 h 0.70745 a 7.8052081,22.886458 0 0 1 -0.70745,-0.147277 z"
|
||||||
|
id="path5334"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</clipPath>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter2516"
|
||||||
|
x="-0.1389541"
|
||||||
|
width="1.2779082"
|
||||||
|
y="-0.54079059"
|
||||||
|
height="2.0815812">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="1.8796256"
|
||||||
|
id="feGaussianBlur2518" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter2492"
|
||||||
|
x="-0.13744183"
|
||||||
|
width="1.2748837"
|
||||||
|
y="-0.68956103"
|
||||||
|
height="2.3791221">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="1.8591692"
|
||||||
|
id="feGaussianBlur2494" />
|
||||||
|
</filter>
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter2504"
|
||||||
|
x="-0.095834583"
|
||||||
|
width="1.1916692"
|
||||||
|
y="-1.248017"
|
||||||
|
height="3.4960341">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="1.710971"
|
||||||
|
id="feGaussianBlur2506" />
|
||||||
|
</filter>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient3040"
|
||||||
|
id="linearGradient2564"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="translate(-1.0583333)"
|
||||||
|
x1="177.82297"
|
||||||
|
y1="66.155998"
|
||||||
|
x2="184.15675"
|
||||||
|
y2="66.155998" />
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient3040"
|
||||||
|
inkscape:collect="always">
|
||||||
|
<stop
|
||||||
|
id="stop3036"
|
||||||
|
offset="0"
|
||||||
|
style="stop-color:#2ca089;stop-opacity:1" />
|
||||||
|
<stop
|
||||||
|
id="stop3038"
|
||||||
|
offset="1"
|
||||||
|
style="stop-color:#93ac93;stop-opacity:0;" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient5548"
|
||||||
|
id="linearGradient2566"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(3.7795277,0,0,2.6941623,-313.61226,-1006.2008)"
|
||||||
|
x1="177.82297"
|
||||||
|
y1="66.155998"
|
||||||
|
x2="184.15675"
|
||||||
|
y2="66.155998" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient5548">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#37c8ab;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop5544" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#93ac93;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop5546" />
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath962">
|
||||||
|
<ellipse
|
||||||
|
style="fill:url(#linearGradient956);fill-opacity:1;stroke-width:0.228428"
|
||||||
|
id="ellipse964"
|
||||||
|
cx="176.34479"
|
||||||
|
cy="66.1511"
|
||||||
|
rx="6.6189275"
|
||||||
|
ry="20.116356" />
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient1636"
|
||||||
|
id="linearGradient2568"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="180.2618"
|
||||||
|
y1="79.843285"
|
||||||
|
x2="177.90164"
|
||||||
|
y2="58.2136" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient1636">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#6f917c;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop1632" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#1c2422;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop1634" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient1988"
|
||||||
|
id="linearGradient2570"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(1.0057529,0,0,0.63337186,1.6167144,24.252855)"
|
||||||
|
x1="180.2618"
|
||||||
|
y1="79.843285"
|
||||||
|
x2="177.90164"
|
||||||
|
y2="58.2136" />
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient1988"
|
||||||
|
inkscape:collect="always">
|
||||||
|
<stop
|
||||||
|
id="stop1984"
|
||||||
|
offset="0"
|
||||||
|
style="stop-color:#6f917c;stop-opacity:1;" />
|
||||||
|
<stop
|
||||||
|
id="stop1986"
|
||||||
|
offset="1"
|
||||||
|
style="stop-color:#b7c8c4;stop-opacity:0.06956521" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient2562"
|
||||||
|
id="linearGradient2572"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="164.83987"
|
||||||
|
y1="43.408844"
|
||||||
|
x2="203.95857"
|
||||||
|
y2="43.128212" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient2562">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#00d4aa;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop2558" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#00d4aa;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop2560" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient3482"
|
||||||
|
id="linearGradient3484"
|
||||||
|
x1="356.72446"
|
||||||
|
y1="182.1709"
|
||||||
|
x2="344.48761"
|
||||||
|
y2="129.496"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="translate(34.475201,-21.166667)" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient3482">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#171e1c;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop3478" />
|
||||||
|
<stop
|
||||||
|
id="stop3494"
|
||||||
|
offset="0.80120975"
|
||||||
|
style="stop-color:#152320;stop-opacity:1;" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#152521;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop3480" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient3490"
|
||||||
|
id="linearGradient3492"
|
||||||
|
x1="347.93649"
|
||||||
|
y1="171.56474"
|
||||||
|
x2="354.54169"
|
||||||
|
y2="126.321"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="translate(34.475201,-21.166667)" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient3490">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#374845;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop3486" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#152a27;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop3488" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient3552"
|
||||||
|
id="linearGradient3554"
|
||||||
|
x1="345.86197"
|
||||||
|
y1="127.05593"
|
||||||
|
x2="350.06058"
|
||||||
|
y2="140.94655"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="translate(34.475201,-21.166667)" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient3552">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#131915;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop3548" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#0a0c0c;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop3550" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.45328694,0,0,0.45328694,-150.08426,31.677789)"
|
||||||
|
id="g3583">
|
||||||
|
<path
|
||||||
|
style="fill:#374845;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 384.08136,149.35152 2.85336,-1.98187 17.3047,-17.53781 5.30222,-23.86564 -1.14434,4.90968 -7.12593,20.49392 z"
|
||||||
|
id="path3476"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#19201f;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 409.63036,105.6806 -6.42418,23.31826 -19.12481,20.35266 -1.02231,-2.62018 13.36763,-19.08194 -0.12652,-9.90058 z"
|
||||||
|
id="path3449"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#374845;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 373.21796,148.73038 -0.80327,-4.14381 -9.92729,-19.61929 2.56866,-5.68006 c 0,0 -2.39194,-2.20708 -2.63096,-1.76366 l -0.23905,0.44342 -1.91789,7.51849 z"
|
||||||
|
id="path3474"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
<ellipse
|
||||||
|
ry="13.768127"
|
||||||
|
rx="15.163113"
|
||||||
|
cy="5.6849346"
|
||||||
|
cx="406.25998"
|
||||||
|
id="ellipse6063"
|
||||||
|
style="opacity:0.34;fill:none;fill-opacity:1;stroke:#2affd5;stroke-width:0.259508"
|
||||||
|
transform="matrix(0.99458815,0.1038962,-0.0109865,0.99993965,0,0)" />
|
||||||
|
<path
|
||||||
|
style="fill:#161d1b;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 353.47041,111.22063 -0.49878,15.54703 15.93949,21.91363 4.30684,0.0491 -12.94981,-23.24491 3.13368,-9.68437 z"
|
||||||
|
id="path3447"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<circle
|
||||||
|
style="fill:url(#radialGradient4635);fill-opacity:1;stroke-width:0.171116"
|
||||||
|
id="path4619"
|
||||||
|
cx="374.65378"
|
||||||
|
cy="80.797707"
|
||||||
|
r="44.979172" />
|
||||||
|
<g
|
||||||
|
id="g4980"
|
||||||
|
clip-path="url(#clipPath4984)"
|
||||||
|
transform="translate(34.475201,-37.041668)">
|
||||||
|
<ellipse
|
||||||
|
transform="matrix(1.5757282,0.23146107,-0.20900143,1.7450588,-197.08653,-112.20092)"
|
||||||
|
style="opacity:0.358;fill:#050e11;fill-opacity:1;stroke-width:0.264583;filter:url(#filter4955)"
|
||||||
|
id="ellipse5828"
|
||||||
|
cx="351.59506"
|
||||||
|
cy="82.067276"
|
||||||
|
rx="23.198996"
|
||||||
|
ry="9.3544331" />
|
||||||
|
<ellipse
|
||||||
|
ry="9.3544331"
|
||||||
|
rx="23.198996"
|
||||||
|
cy="82.067276"
|
||||||
|
cx="351.59506"
|
||||||
|
id="path4929"
|
||||||
|
style="opacity:0.123;fill:#00ccff;fill-opacity:1;stroke-width:0.264583;filter:url(#filter4955)"
|
||||||
|
transform="matrix(1.2075594,0.12977346,-0.16016825,0.9784035,-67.414432,-42.767194)" />
|
||||||
|
<ellipse
|
||||||
|
transform="matrix(0.69536155,0.02706809,-0.01729677,0.58663762,91.984087,21.914596)"
|
||||||
|
style="opacity:0.217;fill:#37c8ab;fill-opacity:1;stroke-width:0.264583;filter:url(#filter3376)"
|
||||||
|
id="ellipse4990"
|
||||||
|
cx="351.59506"
|
||||||
|
cy="82.067276"
|
||||||
|
rx="23.198996"
|
||||||
|
ry="9.3544331" />
|
||||||
|
<path
|
||||||
|
style="opacity:0.081;fill:url(#linearGradient3607);fill-opacity:1;stroke:#dbe3de;stroke-width:1.19274;filter:url(#filter5875)"
|
||||||
|
d="M 1446.4434,435.68945 A 157.04902,125.90364 0 0 1 1290,551.9043 157.04902,125.90364 0 0 1 1133.5566,436.31055 157.04902,125.90364 0 0 0 1132.9512,446 157.04902,125.90364 0 0 0 1290,571.9043 157.04902,125.90364 0 0 0 1447.0488,446 a 157.04902,125.90364 0 0 0 -0.6054,-10.31055 z"
|
||||||
|
transform="matrix(0.26311638,0,0,0.28631922,2.0539852,-2.9301278)"
|
||||||
|
id="path5830"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:#1c2422;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3156)"
|
||||||
|
d="m 309.12314,87.292218 c 3.13441,24.914932 -0.21048,39.337292 -5.82115,37.879702 -5.61067,-1.45759 -11.27628,-19.79646 -6.0807,-25.272072 4.54081,-2.856353 11.90185,-12.60763 11.90185,-12.60763 z"
|
||||||
|
id="path5909"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="czcc"
|
||||||
|
transform="rotate(12.553566,314.41882,114.81993)" />
|
||||||
|
<ellipse
|
||||||
|
transform="matrix(0.52125432,0,0,0.52125432,156.27047,108.66382)"
|
||||||
|
ry="12.170834"
|
||||||
|
rx="11.641666"
|
||||||
|
cy="85.862556"
|
||||||
|
cx="321.73331"
|
||||||
|
id="ellipse3779"
|
||||||
|
style="opacity:0.171;fill:#d7f4d7;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3775)" />
|
||||||
|
<ellipse
|
||||||
|
transform="matrix(0.52125432,0,0,0.52125432,186.60865,50.055978)"
|
||||||
|
ry="12.170834"
|
||||||
|
rx="11.641666"
|
||||||
|
cy="85.862556"
|
||||||
|
cx="321.73331"
|
||||||
|
id="ellipse3779-7"
|
||||||
|
style="opacity:0.171;fill:#d7f4d7;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3822)" />
|
||||||
|
<ellipse
|
||||||
|
style="opacity:0.171;fill:#d7f4d7;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3834)"
|
||||||
|
id="ellipse3812"
|
||||||
|
cx="321.73331"
|
||||||
|
cy="85.862556"
|
||||||
|
rx="11.641666"
|
||||||
|
ry="12.170834"
|
||||||
|
transform="matrix(0.52125432,0,0,0.52125432,208.32803,97.976508)" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.66411258,0.38342558,-0.38342558,0.66411258,232.97954,-70.367332)"
|
||||||
|
id="use4648">
|
||||||
|
<g
|
||||||
|
style="fill:#171d19;fill-opacity:1"
|
||||||
|
transform="matrix(0.88505748,0,0,0.88505748,27.142605,7.5883684)"
|
||||||
|
id="g2504">
|
||||||
|
<ellipse
|
||||||
|
ry="22.886457"
|
||||||
|
rx="7.8052082"
|
||||||
|
cy="66.1511"
|
||||||
|
cx="203.86162"
|
||||||
|
id="ellipse2498"
|
||||||
|
style="fill:url(#linearGradient2554);fill-opacity:1;stroke-width:0.264583" />
|
||||||
|
<rect
|
||||||
|
style="fill:#171d19;fill-opacity:1;stroke-width:0.212422"
|
||||||
|
id="rect2500"
|
||||||
|
width="28.310415"
|
||||||
|
height="46.005398"
|
||||||
|
x="175.41875"
|
||||||
|
y="43.000057" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:#171d19;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
id="ellipse2502"
|
||||||
|
cx="176.34479"
|
||||||
|
cy="66.1511"
|
||||||
|
rx="7.8052082"
|
||||||
|
ry="22.886457" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g2550">
|
||||||
|
<path
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,0,130.31255)"
|
||||||
|
sodipodi:nodetypes="ccccscccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2506"
|
||||||
|
d="M 666.88909,-328.58579 663,-156.12109 h 106.67383 c 0.27518,0.0517 0.5506,0.0921 0.82617,0.12109 16.2924,0 29.5,-38.72737 29.5,-86.5 0,-47.77263 -13.2076,-86.5 -29.5,-86.5 -0.16671,0.0134 -0.33339,0.031 -0.5,0.0527 V -330 Z"
|
||||||
|
style="fill:url(#linearGradient2556);fill-opacity:1;stroke-width:1" />
|
||||||
|
<ellipse
|
||||||
|
ry="22.886457"
|
||||||
|
rx="7.8052082"
|
||||||
|
cy="66.1511"
|
||||||
|
cx="176.34479"
|
||||||
|
id="ellipse2508"
|
||||||
|
style="fill:#37483e;fill-opacity:1;stroke:url(#radialGradient2558);stroke-width:0.264583;stroke-opacity:1" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:url(#linearGradient2560);fill-opacity:1;stroke:none;stroke-width:0.228428;stroke-opacity:1"
|
||||||
|
id="ellipse2510"
|
||||||
|
cx="176.34479"
|
||||||
|
cy="66.1511"
|
||||||
|
rx="6.618928"
|
||||||
|
ry="20.116356" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2512"
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,0,130.31255)"
|
||||||
|
d="m 681.98633,-302.10156 a 25.016421,62.241739 0 0 0 -17.875,59.60156 25.016421,62.241739 0 0 0 17.90234,59.63867 25.016421,76.030324 0 0 0 9.50195,-59.63867 25.016421,76.030324 0 0 0 -9.52929,-59.60156 z"
|
||||||
|
style="fill:url(#radialGradient2562);fill-opacity:1;stroke-width:0.78115" />
|
||||||
|
<g
|
||||||
|
style="opacity:1;fill:#dbe3de;fill-opacity:1"
|
||||||
|
clip-path="url(#clipPath5332)"
|
||||||
|
id="g2522">
|
||||||
|
<g
|
||||||
|
style="opacity:1;fill:#2ad4ff;fill-opacity:1;filter:url(#filter2516)"
|
||||||
|
transform="matrix(1,0,0,1.1345691,-7.7417057e-7,133.5433)"
|
||||||
|
id="g2516">
|
||||||
|
<rect
|
||||||
|
y="-68.680977"
|
||||||
|
x="180.44221"
|
||||||
|
height="8.3416786"
|
||||||
|
width="32.464687"
|
||||||
|
id="rect2514"
|
||||||
|
style="opacity:0.166;fill:#2ad4ff;fill-opacity:1;stroke-width:0.264583" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
y="75.459694"
|
||||||
|
x="180.44945"
|
||||||
|
height="6.4707923"
|
||||||
|
width="32.464687"
|
||||||
|
id="rect2518"
|
||||||
|
style="opacity:0.464;fill:#000c09;fill-opacity:1;stroke-width:0.233031;filter:url(#filter2492)" />
|
||||||
|
<rect
|
||||||
|
y="42.056061"
|
||||||
|
x="172.21031"
|
||||||
|
height="3.2902839"
|
||||||
|
width="42.848106"
|
||||||
|
id="rect2520"
|
||||||
|
style="opacity:0.404;fill:#2ad4ff;fill-opacity:1;stroke-width:0.241515;filter:url(#filter2504)" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:url(#linearGradient2564);fill-opacity:1;stroke-width:0.106961"
|
||||||
|
d="m 179.43157,58.663845 a 3.7324504,7.8216032 0 0 0 -2.66695,7.489825 3.7324504,7.8216032 0 0 0 2.67103,7.494489 3.7324504,9.5543451 0 0 0 1.41769,-7.494489 3.7324504,9.5543451 0 0 0 -1.42177,-7.489825 z"
|
||||||
|
id="path2524" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2526"
|
||||||
|
d="m 186.92813,42.825396 a 7.8052082,23.325705 0 0 1 7.80521,23.325704 7.8052082,23.325705 0 0 1 -7.80521,23.325705"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#1f241c;stroke-width:0.26711" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2528"
|
||||||
|
d="m 181.63646,42.825396 a 7.8052082,23.325705 0 0 1 7.80521,23.325704 7.8052082,23.325705 0 0 1 -7.80521,23.325705"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#1f241c;stroke-width:0.26711" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.111;fill:none;fill-opacity:1;stroke:#dbe3de;stroke-width:0.26711"
|
||||||
|
d="m 187.20875,42.825396 a 7.8052082,23.325705 0 0 1 7.80521,23.325704 7.8052082,23.325705 0 0 1 -7.80521,23.325705"
|
||||||
|
id="path2530" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.111;fill:none;fill-opacity:1;stroke:#dbe3de;stroke-width:0.26711"
|
||||||
|
d="m 181.91708,42.825396 a 7.8052082,23.325705 0 0 1 7.80521,23.325704 7.8052082,23.325705 0 0 1 -7.80521,23.325705"
|
||||||
|
id="path2532" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2534"
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,84.564076,285.22196)"
|
||||||
|
d="m 368.55469,-848.15039 a 14.1069,21.072668 0 0 0 -10.08008,20.17773 14.1069,21.072668 0 0 0 10.0957,20.19141 14.1069,25.740956 0 0 0 2.85547,-5.56445 25.016421,76.030325 0 0 0 0.47852,-14.63868 25.016421,76.030325 0 0 0 -0.48047,-14.58398 14.1069,25.740956 0 0 0 -2.86914,-5.58203 z"
|
||||||
|
style="opacity:0.148;fill:url(#linearGradient2566);fill-opacity:1;stroke-width:0.341316" />
|
||||||
|
<g
|
||||||
|
clip-path="url(#clipPath962)"
|
||||||
|
id="g2546">
|
||||||
|
<path
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:end="4.712389"
|
||||||
|
sodipodi:start="1.5707963"
|
||||||
|
sodipodi:ry="16.29439"
|
||||||
|
sodipodi:rx="6.6350451"
|
||||||
|
sodipodi:cy="66.1511"
|
||||||
|
sodipodi:cx="182.26727"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
id="path2536"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:url(#linearGradient2568);stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="m 182.26727,82.44549 a 6.6350451,16.29439 0 0 1 -5.74611,-8.147195 6.6350451,16.29439 0 0 1 0,-16.29439 6.6350451,16.29439 0 0 1 5.74611,-8.147195" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#091f1b;stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path2538"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="177.40311"
|
||||||
|
sodipodi:cy="66.1511"
|
||||||
|
sodipodi:rx="6.618928"
|
||||||
|
sodipodi:ry="20.116356"
|
||||||
|
sodipodi:start="1.5707963"
|
||||||
|
sodipodi:end="4.712389"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="m 177.40311,86.267456 a 6.618928,20.116356 0 0 1 -5.73216,-10.058178 6.618928,20.116356 0 0 1 0,-20.116356 6.618928,20.116356 0 0 1 5.73216,-10.058178" />
|
||||||
|
<path
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:end="4.712389"
|
||||||
|
sodipodi:start="1.5707963"
|
||||||
|
sodipodi:ry="18.995991"
|
||||||
|
sodipodi:rx="6.6234765"
|
||||||
|
sodipodi:cy="66.1511"
|
||||||
|
sodipodi:cx="178.9929"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
id="path2540"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#091f1b;stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="m 178.9929,85.147091 a 6.6234765,18.995991 0 0 1 -5.73609,-9.497995 6.6234765,18.995991 0 0 1 0,-18.995991 6.6234765,18.995991 0 0 1 5.73609,-9.497996" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#091f1b;stroke-width:0.264583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path2542"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="180.58263"
|
||||||
|
sodipodi:cy="66.1511"
|
||||||
|
sodipodi:rx="6.6278849"
|
||||||
|
sodipodi:ry="17.941591"
|
||||||
|
sodipodi:start="1.5707963"
|
||||||
|
sodipodi:end="4.712389"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="m 180.58263,84.092691 a 6.6278849,17.941591 0 0 1 -5.73992,-8.970795 6.6278849,17.941591 0 0 1 0,-17.941592 6.6278849,17.941591 0 0 1 5.73992,-8.970795" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:url(#linearGradient2570);stroke-width:0.211173;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path2544"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="184.93251"
|
||||||
|
sodipodi:cy="66.1511"
|
||||||
|
sodipodi:rx="6.6732159"
|
||||||
|
sodipodi:ry="10.320408"
|
||||||
|
sodipodi:start="1.5707963"
|
||||||
|
sodipodi:end="4.712389"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="m 184.93251,76.471508 a 6.6732159,10.320408 0 0 1 -5.77917,-5.160204 6.6732159,10.320408 0 0 1 0,-10.320408 6.6732159,10.320408 0 0 1 5.77917,-5.160204" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2548"
|
||||||
|
d="m 176.44064,42.995921 27.51666,0.264584"
|
||||||
|
style="fill:none;stroke:url(#linearGradient2572);stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<ellipse
|
||||||
|
transform="rotate(-14.215013)"
|
||||||
|
ry="5.5191154"
|
||||||
|
rx="2.8998742"
|
||||||
|
cy="174.85312"
|
||||||
|
cx="304.87619"
|
||||||
|
id="ellipse2002"
|
||||||
|
style="opacity:1;fill:#113f35;fill-opacity:1;stroke:none;stroke-width:0.264583" />
|
||||||
|
<ellipse
|
||||||
|
style="opacity:1;fill:#161c1a;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||||
|
id="path6286"
|
||||||
|
cx="304.74963"
|
||||||
|
cy="174.27518"
|
||||||
|
rx="2.8998742"
|
||||||
|
ry="5.5191154"
|
||||||
|
transform="rotate(-14.215013)" />
|
||||||
|
<path
|
||||||
|
style="fill:#131915;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 370.79736,119.67173 c 1.04003,1.20435 8.7148,-9.18437 13.02347,-22.625767 l 6.40568,2.29773 c 0,0 -10.44064,21.524307 -14.06159,22.372227 z"
|
||||||
|
id="path3496"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:url(#linearGradient3484);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 382.80782,101.31287 4.09249,31.0529 -12.6907,29.28207 -3.62643,-2.08568 8.74484,-28.46283 -3.78779,-15.94331 c -0.0211,-0.0211 1.96246,-3.15061 7.26759,-13.84315 z"
|
||||||
|
id="path3445"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:url(#linearGradient3492);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 384.13075,102.10662 3.01386,-0.41664 2.53371,30.54079 -13.5599,26.92136 -1.90881,2.49571 12.69072,-29.28207 z"
|
||||||
|
id="path3472"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:url(#linearGradient3554);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 375.44429,115.21775 2.52847,9.81712 7.73208,-6.39815 c 0,0 3.52257,-0.21254 3.24195,-1.14798 -0.28064,8.77282 -1.34172,-14.89948 -1.34172,-14.89948 l -3.97428,-3.277937 z"
|
||||||
|
id="path3498"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 33 KiB |
|
@ -268,3 +268,39 @@ $sidebar_border: transparentize(white, 0.95);
|
||||||
$color: mix($wisegreen, $content_bg, 30%);
|
$color: mix($wisegreen, $content_bg, 30%);
|
||||||
background: linear-gradient(170deg, lighten($color, 10%), $color);
|
background: linear-gradient(170deg, lighten($color, 10%), $color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin flow_grid {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax($cell - 5, 1fr));
|
||||||
|
grid-auto-rows: $cell;
|
||||||
|
|
||||||
|
grid-auto-flow: row dense;
|
||||||
|
grid-column-gap: $gap;
|
||||||
|
grid-row-gap: $gap;
|
||||||
|
|
||||||
|
@media (max-width: ($cell + 10) * 3) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax($fluid_cell - 20, 1fr));
|
||||||
|
grid-auto-rows: $fluid_cell;
|
||||||
|
grid-template-rows: calc(50vw - 10px) $fluid_cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $cell_tablet) {
|
||||||
|
grid-template-rows: calc(66vw - 10px) auto $fluid_cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $cell_mobile) {
|
||||||
|
// rework stamp, so it will be shown as smaller one on mobiles
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(50vw - 20px), 1fr));
|
||||||
|
grid-template-rows: calc(80vw - 10px) auto 50vw;
|
||||||
|
grid-auto-rows: 50vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: ($fluid_cell + 5) * 1.5 + 20) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(calc(50vw - 20px), 1fr));
|
||||||
|
grid-template-rows: calc(80vw - 10px) auto 50vw;
|
||||||
|
grid-auto-rows: 50vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
13
src/utils/color.ts
Normal file
13
src/utils/color.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export const convertHexToRGBA = (hexCode, opacity) => {
|
||||||
|
let hex = hexCode.replace('#', '');
|
||||||
|
|
||||||
|
if (hex.length === 3) {
|
||||||
|
hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = parseInt(hex.substring(0, 2), 16);
|
||||||
|
const g = parseInt(hex.substring(2, 4), 16);
|
||||||
|
const b = parseInt(hex.substring(4, 6), 16);
|
||||||
|
|
||||||
|
return `rgba(${r},${g},${b},${opacity})`;
|
||||||
|
};
|
|
@ -1,23 +1,10 @@
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { flowGetMore } from '~/redux/flow/actions';
|
import { flowGetMore } from '~/redux/flow/actions';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useInfiniteLoader } from '~/utils/hooks/useInfiniteLoader';
|
||||||
|
|
||||||
export const useFlowPagination = ({ isLoading }) => {
|
export const useFlowPagination = ({ isLoading }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const loadMore = useCallback(() => dispatch(flowGetMore()), []);
|
||||||
const onLoadMore = useCallback(() => {
|
useInfiniteLoader(loadMore, isLoading);
|
||||||
(window as any).flowScrollPos = window.scrollY;
|
|
||||||
|
|
||||||
const pos = window.scrollY + window.innerHeight - document.body.scrollHeight;
|
|
||||||
|
|
||||||
if (isLoading || pos < -600) return;
|
|
||||||
|
|
||||||
dispatch(flowGetMore());
|
|
||||||
}, [dispatch, isLoading]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('scroll', onLoadMore);
|
|
||||||
|
|
||||||
return () => window.removeEventListener('scroll', onLoadMore);
|
|
||||||
}, [onLoadMore]);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,10 +39,12 @@ export const useDelayedReady = (setReady: (val: boolean) => void, delay: number
|
||||||
* @param onUpload -- upload callback
|
* @param onUpload -- upload callback
|
||||||
* @param allowedTypes -- list of allowed types
|
* @param allowedTypes -- list of allowed types
|
||||||
*/
|
*/
|
||||||
export const useDropZone = (onUpload: (file: File[]) => void, allowedTypes?: string[]) => {
|
export const useFileDropZone = (onUpload: (file: File[]) => void, allowedTypes?: string[]) => {
|
||||||
return useCallback(
|
return useCallback(
|
||||||
event => {
|
event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
const files: File[] = Array.from((event.dataTransfer?.files as File[]) || []).filter(
|
const files: File[] = Array.from((event.dataTransfer?.files as File[]) || []).filter(
|
||||||
(file: File) => file?.type && (!allowedTypes || allowedTypes.includes(file.type))
|
(file: File) => file?.type && (!allowedTypes || allowedTypes.includes(file.type))
|
||||||
);
|
);
|
||||||
|
|
21
src/utils/hooks/lab/useLabPagination.ts
Normal file
21
src/utils/hooks/lab/useLabPagination.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useInfiniteLoader } from '~/utils/hooks/useInfiniteLoader';
|
||||||
|
import { labGetMore } from '~/redux/lab/actions';
|
||||||
|
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||||
|
import { selectLabList } from '~/redux/lab/selectors';
|
||||||
|
|
||||||
|
export const useLabPagination = ({ isLoading }) => {
|
||||||
|
const { nodes, count } = useShallowSelect(selectLabList);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const loadMore = useCallback(() => {
|
||||||
|
if (nodes.length >= count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(labGetMore());
|
||||||
|
}, [nodes, count]);
|
||||||
|
|
||||||
|
useInfiniteLoader(loadMore, isLoading);
|
||||||
|
};
|
|
@ -2,7 +2,7 @@ import { IComment, INode } from '~/redux/types';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||||
import { array, object, string } from 'yup';
|
import { array, object, string } from 'yup';
|
||||||
import { FileUploader } from '~/utils/hooks/fileUploader';
|
import { FileUploader } from '~/utils/hooks/useFileUploader';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { nodePostLocalComment } from '~/redux/node/actions';
|
import { nodePostLocalComment } from '~/redux/node/actions';
|
||||||
|
|
||||||
|
|
50
src/utils/hooks/useDragDetector.tsx
Normal file
50
src/utils/hooks/useDragDetector.tsx
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import React, { FC, useContext } from 'react';
|
||||||
|
import { createContext, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const DragContext = createContext({
|
||||||
|
isDragging: false,
|
||||||
|
setIsDragging: (val: boolean) => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DragDetectorProvider: FC = ({ children }) => {
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragContext.Provider value={{ isDragging, setIsDragging }}>{children}</DragContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDragDetector = () => {
|
||||||
|
const { isDragging, setIsDragging } = useContext(DragContext);
|
||||||
|
|
||||||
|
const onStopDragging = useCallback(() => setIsDragging(false), [setIsDragging]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const addClass = () => setIsDragging(true);
|
||||||
|
|
||||||
|
const removeClass = event => {
|
||||||
|
// Small hack to ignore intersection with child elements
|
||||||
|
if (event.pageX !== 0 && event.pageY !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsDragging(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('dragenter', addClass);
|
||||||
|
document.addEventListener('dragover', addClass);
|
||||||
|
document.addEventListener('dragleave', removeClass);
|
||||||
|
document.addEventListener('blur', removeClass);
|
||||||
|
document.addEventListener('drop', onStopDragging);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('dragenter', addClass);
|
||||||
|
document.removeEventListener('dragover', addClass);
|
||||||
|
document.removeEventListener('dragleave', removeClass);
|
||||||
|
document.removeEventListener('blur', removeClass);
|
||||||
|
document.removeEventListener('drop', onStopDragging);
|
||||||
|
};
|
||||||
|
}, [setIsDragging]);
|
||||||
|
|
||||||
|
return { isDragging, onStopDragging };
|
||||||
|
};
|
17
src/utils/hooks/useInfiniteLoader.ts
Normal file
17
src/utils/hooks/useInfiniteLoader.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
export const useInfiniteLoader = (loader: () => void, isLoading?: boolean) => {
|
||||||
|
const onLoadMore = useCallback(() => {
|
||||||
|
const pos = window.scrollY + window.innerHeight - document.body.scrollHeight;
|
||||||
|
|
||||||
|
if (isLoading || pos < -600) return;
|
||||||
|
|
||||||
|
loader();
|
||||||
|
}, [loader, isLoading]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('scroll', onLoadMore);
|
||||||
|
|
||||||
|
return () => window.removeEventListener('scroll', onLoadMore);
|
||||||
|
}, [onLoadMore]);
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
import { IComment, INode } from '~/redux/types';
|
import { IComment, INode } from '~/redux/types';
|
||||||
import { FileUploader } from '~/utils/hooks/fileUploader';
|
import { FileUploader } from '~/utils/hooks/useFileUploader';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||||
import { object, string } from 'yup';
|
import { object, string } from 'yup';
|
||||||
|
|
21
src/utils/hooks/user/useUserDescription.ts
Normal file
21
src/utils/hooks/user/useUserDescription.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { IUser } from '~/redux/auth/types';
|
||||||
|
import { useRandomPhrase } from '~/constants/phrases';
|
||||||
|
import { differenceInDays, parseISO } from 'date-fns';
|
||||||
|
import { INACTIVE_ACCOUNT_DAYS } from '~/constants/user';
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
|
export const useUserDescription = (user?: Partial<IUser>) => {
|
||||||
|
const randomPhrase = useRandomPhrase('USER_DESCRIPTION');
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastSeen = user.last_seen ? parseISO(user.last_seen) : undefined;
|
||||||
|
if (!lastSeen || differenceInDays(today, lastSeen) > INACTIVE_ACCOUNT_DAYS) {
|
||||||
|
return 'Юнит деактивирован';
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.description || randomPhrase;
|
||||||
|
};
|
|
@ -4,3 +4,5 @@ export type DivProps = React.DetailedHTMLProps<
|
||||||
React.HTMLAttributes<HTMLDivElement>,
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
HTMLDivElement
|
HTMLDivElement
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type SVGProps = React.SVGProps<SVGSVGElement>;
|
||||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -1805,6 +1805,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/jest" "*"
|
"@types/jest" "*"
|
||||||
|
|
||||||
|
"@types/throttle-debounce@^2.1.0":
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776"
|
||||||
|
integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "15.0.0"
|
version "15.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
||||||
|
@ -2410,6 +2415,11 @@ atob@^2.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||||
|
|
||||||
|
attr-accept@^2.2.1:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
|
||||||
|
integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
|
||||||
|
|
||||||
autoprefixer@^9.6.1:
|
autoprefixer@^9.6.1:
|
||||||
version "9.8.6"
|
version "9.8.6"
|
||||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f"
|
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f"
|
||||||
|
@ -4885,6 +4895,13 @@ file-loader@4.3.0:
|
||||||
loader-utils "^1.2.3"
|
loader-utils "^1.2.3"
|
||||||
schema-utils "^2.5.0"
|
schema-utils "^2.5.0"
|
||||||
|
|
||||||
|
file-selector@^0.2.2:
|
||||||
|
version "0.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.2.4.tgz#7b98286f9dbb9925f420130ea5ed0a69238d4d80"
|
||||||
|
integrity sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.0.3"
|
||||||
|
|
||||||
file-uri-to-path@1.0.0:
|
file-uri-to-path@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||||
|
@ -9361,6 +9378,15 @@ react-dom@^17.0.1:
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
scheduler "^0.20.1"
|
scheduler "^0.20.1"
|
||||||
|
|
||||||
|
react-dropzone@^11.4.2:
|
||||||
|
version "11.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-11.4.2.tgz#1eb99e9def4cc7520f4f58e85c853ce52c483d56"
|
||||||
|
integrity sha512-ocYzYn7Qgp0tFc1gQtUTOaHHSzVTwhWHxxY+r7cj2jJTPfMTZB5GWSJHdIVoxsl+EQENpjJ/6Zvcw0BqKZQ+Eg==
|
||||||
|
dependencies:
|
||||||
|
attr-accept "^2.2.1"
|
||||||
|
file-selector "^0.2.2"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-error-overlay@^6.0.7:
|
react-error-overlay@^6.0.7:
|
||||||
version "6.0.8"
|
version "6.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.8.tgz#474ed11d04fc6bda3af643447d85e9127ed6b5de"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.8.tgz#474ed11d04fc6bda3af643447d85e9127ed6b5de"
|
||||||
|
@ -11137,6 +11163,11 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
|
tslib@^2.0.3:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||||
|
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||||
|
|
||||||
tsutils@^3.17.1:
|
tsutils@^3.17.1:
|
||||||
version "3.17.1"
|
version "3.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
|
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue