1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-28 22:26:41 +07:00

Compare commits

..

No commits in common. "9e79cba7bffb1cf340c871c77613e63f56e9302a" and "f0606a894a3291b3beb399ba4a14e9c973b04b24" have entirely different histories.

21 changed files with 243 additions and 250 deletions

View file

@ -27,8 +27,8 @@
&.new { &.new {
&::after { &::after {
content: ' '; content: ' ';
width: 8px; width: 12px;
height: 8px; height: 12px;
border-radius: 100%; border-radius: 100%;
background: $color_danger; background: $color_danger;
box-shadow: $content_bg 0 0 0 5px; box-shadow: $content_bg 0 0 0 5px;

View file

@ -13,11 +13,9 @@ interface Props extends DivProps {
const SubTitle: FC<Props> = ({ isLoading, children, ...rest }) => ( const SubTitle: FC<Props> = ({ isLoading, children, ...rest }) => (
<div {...rest} className={classNames(styles.title, rest.className)}> <div {...rest} className={classNames(styles.title, rest.className)}>
<span className={styles.name}>
<Placeholder active={isLoading} loading> <Placeholder active={isLoading} loading>
{children} {children}
</Placeholder> </Placeholder>
</span>
</div> </div>
); );

View file

@ -1,25 +1,7 @@
@import 'src/styles/variables.scss'; @import "src/styles/variables.scss";
.title { .title {
font: $font_12_semibold; font: $font_12_semibold;
text-transform: uppercase; text-transform: uppercase;
display: flex; opacity: 0.3;
flex-direction: row;
align-items: center;
gap: $gap / 2;
color: var(--gray_75);
a {
text-decoration: none;
color: inherit;
}
&::after {
content: ' ';
display: flex;
height: 2px;
background-color: var(--gray_90);
flex: 1;
border-radius: 2px;
}
} }

View file

@ -29,6 +29,11 @@
.title { .title {
padding-left: 5px; padding-left: 5px;
a {
text-decoration: none;
color: inherit;
}
} }
.text { .text {

View file

@ -5,5 +5,3 @@ export const isTablet = () => {
return window.innerWidth < 599; return window.innerWidth < 599;
}; };
export const headerHeight = 64; // px

View file

@ -23,7 +23,6 @@ interface Props {
const FlowCellMenu: FC<Props> = ({ const FlowCellMenu: FC<Props> = ({
onClose, onClose,
currentView,
hasDescription, hasDescription,
toggleViewDescription, toggleViewDescription,
descriptionEnabled, descriptionEnabled,
@ -60,7 +59,7 @@ const FlowCellMenu: FC<Props> = ({
/> />
</div> </div>
{hasDescription && currentView !== 'single' && ( {hasDescription && (
<Group <Group
className={styles.description} className={styles.description}
horizontal horizontal

View file

@ -1,7 +1,6 @@
import { FC, ReactElement } from 'react'; import { FC, ReactElement } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { transparentize, darken, desaturate, getLuminance } from 'color2k';
import { Markdown } from '~/components/common/Markdown'; import { Markdown } from '~/components/common/Markdown';
import { formatText } from '~/utils/dom'; import { formatText } from '~/utils/dom';
@ -12,29 +11,13 @@ import styles from './styles.module.scss';
interface Props extends DivProps { interface Props extends DivProps {
children: string; children: string;
heading: string | ReactElement; heading: string | ReactElement;
color?: string;
} }
const FlowCellText: FC<Props> = ({ children, heading, color, ...rest }) => { const FlowCellText: FC<Props> = ({ children, heading, ...rest }) => (
const colorIsBright = !!color && getLuminance(color) > 0.4; <div {...rest} className={classNames(styles.text, rest.className)}>
const textColor = colorIsBright
? desaturate(darken(color, 0.5), 0.1)
: undefined;
return (
<div
{...rest}
className={classNames(styles.text, rest.className)}
style={{
backgroundColor: color && transparentize(color, 0.5),
color: textColor,
}}
>
{heading && <div className={styles.heading}>{heading}</div>} {heading && <div className={styles.heading}>{heading}</div>}
<Markdown className={styles.description}>{formatText(children)}</Markdown> <Markdown className={styles.description}>{formatText(children)}</Markdown>
</div> </div>
); );
};
export { FlowCellText }; export { FlowCellText };

View file

@ -1,24 +1,10 @@
@import 'src/styles/variables'; @import "src/styles/variables";
.text { .text {
@include blur; padding: $gap;
line-height: 1.3em; line-height: 1.3em;
display: flex;
flex-direction: column;
min-height: 0;
}
.description {
mask-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 1) 50%,
rgba(0, 0, 0, 0) 95%
);
flex: 1;
overflow: hidden;
} }
.heading { .heading {
margin-bottom: 0.25em; margin-bottom: 0.4em;
} }

View file

@ -1,4 +1,4 @@
import { FC, useCallback, useMemo } from 'react'; import { FC, useMemo } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
@ -9,8 +9,6 @@ import { useWindowSize } from '~/hooks/dom/useWindowSize';
import { useFlowCellControls } from '~/hooks/flow/useFlowCellControls'; import { useFlowCellControls } from '~/hooks/flow/useFlowCellControls';
import { FlowDisplay, INode } from '~/types'; import { FlowDisplay, INode } from '~/types';
import { isFullyVisible } from '../../../../../utils/dom';
import { CellShade } from './components/CellShade'; import { CellShade } from './components/CellShade';
import { FlowCellImage } from './components/FlowCellImage'; import { FlowCellImage } from './components/FlowCellImage';
import { FlowCellMenu } from './components/FlowCellMenu'; import { FlowCellMenu } from './components/FlowCellMenu';
@ -27,7 +25,7 @@ interface Props {
text?: string; text?: string;
flow: FlowDisplay; flow: FlowDisplay;
canEdit?: boolean; canEdit?: boolean;
onChange: (id: INode['id'], flow: FlowDisplay) => void; onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void;
} }
const FlowCell: FC<Props> = ({ const FlowCell: FC<Props> = ({
@ -39,7 +37,7 @@ const FlowCell: FC<Props> = ({
text, text,
title, title,
canEdit = false, canEdit = false,
onChange, onChangeCellView,
}) => { }) => {
const { isTablet } = useWindowSize(); const { isTablet } = useWindowSize();
@ -47,30 +45,6 @@ const FlowCell: FC<Props> = ({
((!!flow.display && flow.display !== 'single') || !image) && ((!!flow.display && flow.display !== 'single') || !image) &&
flow.show_description && flow.show_description &&
!!text; !!text;
const {
isActive: isMenuActive,
activate,
ref,
deactivate,
} = useClickOutsideFocus();
const onChangeWithScroll = useCallback<typeof onChange>(
(...args) => {
onChange(...args);
setTimeout(() => {
if (!isFullyVisible(ref.current)) {
ref.current?.scrollIntoView({
behavior: 'auto',
block: 'center',
});
}
}, 0);
},
[onChange, ref],
);
const { const {
hasDescription, hasDescription,
setViewHorizontal, setViewHorizontal,
@ -78,7 +52,13 @@ const FlowCell: FC<Props> = ({
setViewQuadro, setViewQuadro,
setViewSingle, setViewSingle,
toggleViewDescription, toggleViewDescription,
} = useFlowCellControls(id, text, flow, onChangeWithScroll); } = useFlowCellControls(id, text, flow, onChangeCellView);
const {
isActive: isMenuActive,
activate,
ref,
deactivate,
} = useClickOutsideFocus();
const shadeSize = useMemo(() => { const shadeSize = useMemo(() => {
const min = isTablet ? 10 : 15; const min = isTablet ? 10 : 15;
@ -131,9 +111,8 @@ const FlowCell: FC<Props> = ({
<FlowCellText <FlowCellText
className={styles.text} className={styles.text}
heading={<h4 className={styles.title}>{title}</h4>} heading={<h4 className={styles.title}>{title}</h4>}
color={color}
> >
{text} {text!}
</FlowCellText> </FlowCellText>
)} )}
@ -145,7 +124,7 @@ const FlowCell: FC<Props> = ({
/> />
)} )}
{!!title && !withText && ( {!!title && (
<CellShade <CellShade
color={color} color={color}
className={styles.shade} className={styles.shade}

View file

@ -1,7 +1,5 @@
@import 'src/styles/variables'; @import 'src/styles/variables';
$compact_size: 200px;
.cell { .cell {
@include inner_shadow; @include inner_shadow;
@ -11,7 +9,6 @@ $compact_size: 200px;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: $content_bg; background: $content_bg;
container: cell / inline-size;
} }
.thumb { .thumb {
@ -36,17 +33,20 @@ $compact_size: 200px;
.text { .text {
position: absolute; position: absolute;
bottom: 5px;
left: 5px;
z-index: 1; z-index: 1;
overflow: hidden; overflow: hidden;
border-radius: $radius; border-radius: $radius;
max-height: calc(100% - 10px);
max-width: calc(100% - 10px);
box-sizing: border-box; box-sizing: border-box;
inset: 50% 0 0 0; font: $font_16_regular;
padding: $gap $gap * 1.5 0 $gap * 1.5;
font: $font_14_medium;
line-height: 1.25em;
@container (max-width: $compact_size) { @include tablet {
padding: $gap / 2 $gap 0 $gap; font: $font_14_regular;
left: 5px;
bottom: 5px;
} }
& :global(.grey) { & :global(.grey) {
@ -54,34 +54,14 @@ $compact_size: 200px;
opacity: 0.5; opacity: 0.5;
} }
@container (max-width: #{$compact_size}) { .quadro &,
padding: $gap / 2 $gap 0 $gap;
}
.horizontal &,
.quadro & {
@container (max-width: #{$compact_size * 2}) {
padding: $gap / 2 $gap 0 $gap;
}
}
.horizontal & { .horizontal & {
inset: 0 calc(50% + $gap / 2) 0 0; max-width: calc(50% - 15px);
border-radius: $radius 0 0 $radius;
} }
.quadro &,
.vertical & { .vertical & {
inset: calc(50% + $gap / 2) 0 0 0; max-height: calc(50% - 15px);
border-radius: 0 0 $radius $radius;
}
.quadro & {
inset: calc(50% + $gap / 2) calc(50% + $gap / 2) 0 0;
border-radius: 0 $radius 0 $radius;
}
.title {
margin-bottom: 0.1em;
} }
} }
@ -96,21 +76,11 @@ $compact_size: 200px;
.title { .title {
font: $font_cell_title; font: $font_cell_title;
line-height: 1.2em;
text-transform: uppercase; text-transform: uppercase;
word-break: break-word; word-break: break-word;
color: inherit;
margin-bottom: -0.125em;
@container (max-width: #{$compact_size}) { @include tablet {
font: $font_cell_title_compact; font: $font_18_semibold;
}
.horizontal &,
.quadro & {
@container (max-width: #{$compact_size * 2}) {
font: $font_cell_title_compact;
}
} }
} }
@ -137,7 +107,12 @@ $compact_size: 200px;
} }
.display_modal { .display_modal {
@include appear;
position: absolute; position: absolute;
inset: 0; top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 11; z-index: 11;
} }

View file

@ -46,7 +46,7 @@ export const FlowGrid: FC<Props> = observer(
text={node.description} text={node.description}
title={node.title} title={node.title}
canEdit={fetched && isUser && canEditNode(node, user)} canEdit={fetched && isUser && canEditNode(node, user)}
onChange={onChangeCellView} onChangeCellView={onChangeCellView}
/> />
</div> </div>
))} ))}

View file

@ -1,5 +1,11 @@
@import 'src/styles/variables'; @import 'src/styles/variables';
@mixin mobile {
@media (max-width: $cell * 2) {
@content;
}
}
.cell { .cell {
&.horizontal, &.horizontal,
&.quadro { &.quadro {

View file

@ -0,0 +1,33 @@
import { FC } from 'react';
import { NodeHorizontalCard } from '~/components/common/NodeHorizontalCard';
import { IFlowNode } from '~/types';
import styles from './styles.module.scss';
interface Props {
recent: IFlowNode[];
updated: IFlowNode[];
}
const FlowRecent: FC<Props> = ({ recent, updated }) => {
return (
<>
<div className={styles.updates}>
{updated &&
updated.map((node) => (
<NodeHorizontalCard node={node} key={node.id} hasNew />
))}
</div>
<div className={styles.recent}>
{recent &&
recent.map((node) => (
<NodeHorizontalCard node={node} key={node.id} />
))}
</div>
</>
);
};
export { FlowRecent };

View file

@ -0,0 +1,7 @@
@import 'src/styles/variables';
.recent {
@container sizer (width < #{$flow_hide_recents}) {
display: none;
}
}

View file

@ -2,15 +2,17 @@ import { FC, FormEvent, useCallback, useMemo } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Card } from '~/components/common/Card'; import { Group } from '~/components/common/Group';
import { Icon } from '~/components/common/Icon'; import { Icon } from '~/components/common/Icon';
import { NodeHorizontalCard } from '~/components/common/NodeHorizontalCard'; import { Superpower } from '~/components/common/Superpower';
import { SubTitle } from '~/components/common/SubTitle';
import { InputText } from '~/components/input/InputText'; import { InputText } from '~/components/input/InputText';
import { Toggle } from '~/components/input/Toggle';
import { experimentalFeatures } from '~/constants/features';
import styles from '~/containers/flow/FlowStamp/styles.module.scss'; import styles from '~/containers/flow/FlowStamp/styles.module.scss';
import { useFlowContext } from '~/utils/providers/FlowProvider'; import { useFlowContext } from '~/utils/providers/FlowProvider';
import { useSearchContext } from '~/utils/providers/SearchProvider'; import { useSearchContext } from '~/utils/providers/SearchProvider';
import { FlowRecent } from './components/FlowRecent';
import { FlowSearchResults } from './components/FlowSearchResults'; import { FlowSearchResults } from './components/FlowSearchResults';
interface Props { interface Props {
@ -62,8 +64,7 @@ const FlowStamp: FC<Props> = ({ isFluid, onToggleLayout }) => {
return ( return (
<div className={styles.wrap}> <div className={styles.wrap}>
<Card className={styles.search}> <form className={styles.search} onSubmit={onSearchSubmit}>
<form onSubmit={onSearchSubmit}>
<InputText <InputText
title="Поиск" title="Поиск"
value={searchText} value={searchText}
@ -72,11 +73,14 @@ const FlowStamp: FC<Props> = ({ isFluid, onToggleLayout }) => {
onKeyUp={onKeyUp} onKeyUp={onKeyUp}
/> />
</form> </form>
</Card>
{searchText ? ( {searchText ? (
<Card className={styles.grid}> <div className={styles.search_results}>
<SubTitle>Результаты поиска</SubTitle> <div className={styles.grid}>
<div className={styles.label}>
<span className={styles.label_text}>Результаты поиска</span>
<span className="line" />
</div>
<div className={styles.items}> <div className={styles.items}>
<FlowSearchResults <FlowSearchResults
@ -86,31 +90,34 @@ const FlowStamp: FC<Props> = ({ isFluid, onToggleLayout }) => {
onLoadMore={onSearchLoadMore} onLoadMore={onSearchLoadMore}
/> />
</div> </div>
</Card> </div>
</div>
) : ( ) : (
<Card <div className={styles.grid}>
className={classNames(styles.grid, { <div className={classNames(styles.label, styles.whatsnew)}>
[styles.noUpdates]: !updates.length, <span className={styles.label_text}>Что нового?</span>
})} <span className="line" />
</div>
<div className={styles.items}>
<FlowRecent updated={updates} recent={recent} />
</div>
</div>
)}
{experimentalFeatures.liquidFlow && (
<Superpower>
<div className={styles.toggles}>
<Group
horizontal
onClick={onToggleLayout}
className={styles.fluid_toggle}
> >
<SubTitle>Что нового?</SubTitle> <Toggle value={isFluid} />
<div className={styles.toggles__label}>Жидкое течение</div>
{updates.length > 0 && ( </Group>
<div className={classNames(styles.items, styles.updates)}>
{updates.map((node) => (
<NodeHorizontalCard node={node} key={node.id} hasNew />
))}
</div> </div>
)} </Superpower>
{recent.length > 0 && (
<div className={classNames(styles.items, styles.recent)}>
{recent.map((node) => (
<NodeHorizontalCard node={node} key={node.id} />
))}
</div>
)}
</Card>
)} )}
</div> </div>
); );

View file

@ -1,24 +1,22 @@
@import '~/styles/variables'; @import '../../../styles/variables';
.wrap { .wrap {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
gap: $gap; border-radius: $radius;
}
.search {
background-color: var(--content_bg_lighter);
} }
.grid { .grid {
@include outer_shadow();
display: flex; display: flex;
justify-content: stretch; justify-content: stretch;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
border-radius: $radius;
position: relative;
background: $content_bg;
overflow: hidden; overflow: hidden;
gap: $gap;
padding: $gap;
&::after { &::after {
content: ''; content: '';
@ -35,25 +33,49 @@
display: none; display: none;
} }
} }
&.noUpdates {
@container sizer (width < #{$flow_hide_recents}) {
display: none;
}
}
}
.items.recent {
@container sizer (width < #{$flow_hide_recents}) {
display: none;
background-color: red;
}
} }
.items { .items {
padding: 0 $gap 0 $gap;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
}
.label {
display: flex;
flex-direction: row;
min-width: 0;
padding: $gap;
border-radius: $radius;
@include title_with_line();
color: transparentize(white, $amount: 0.8);
&_search {
color: white;
padding-left: $gap * 1.2;
}
& > :global(.line) {
margin-right: $gap;
}
}
.label_text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search {
@include outer_shadow();
background: $content_bg_lighter;
padding: $gap;
border-radius: $radius;
} }
.search_icon { .search_icon {
@ -67,3 +89,34 @@
stroke-width: 0.5; stroke-width: 0.5;
transition: opacity 0.25s; transition: opacity 0.25s;
} }
.toggles {
& > div {
padding: $gap;
font: $font_14_semibold;
}
&__label {
cursor: pointer;
}
}
.fluid_toggle {
@include desktop {
display: none;
}
}
.whatsnew {
@container sizer (width < #{$flow_hide_recents}) {
display: none;
}
}
.search_results {
overflow: auto;
@include tablet {
margin-top: $gap;
}
}

View file

@ -30,7 +30,7 @@ $cols: math.div($content_width, $cell);
min-height: 200px; min-height: 200px;
@include flow_grid() { @include flow_grid() {
grid-template-rows: min(50vh, 33cqw); grid-template-rows: 40vh;
} }
@include flow_breakpoint(5); @include flow_breakpoint(5);

View file

@ -1,3 +1,4 @@
$bold: 700; $bold: 700;
$semibold: 600; $semibold: 600;
$regular: 400; $regular: 400;
@ -5,19 +6,9 @@ $medium: 500;
$light: 300; $light: 300;
$extra_light: 200; $extra_light: 200;
$font:
Montserrat, $font: Montserrat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
-apple-system, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
Arial,
'Noto Sans',
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
'Noto Color Emoji'; 'Noto Color Emoji';
$font_48_semibold: $semibold 48px $font; $font_48_semibold: $semibold 48px $font;
@ -48,6 +39,5 @@ $font_8_regular: $regular 8px $font;
$font_8_semibold: $semibold 8px $font; $font_8_semibold: $semibold 8px $font;
$font_cell_title: $font_24_semibold; $font_cell_title: $font_24_semibold;
$font_cell_title_compact: $font_18_semibold;
$font_hero_title: $bold 40px $font; $font_hero_title: $bold 40px $font;
$font_boris: $bold 72px $font; $font_boris: $bold 72px $font;

View file

@ -63,8 +63,10 @@ $_brown: #23201f;
--content_bg_success: #{transparentize($_wisegreen, 0.7)}; --content_bg_success: #{transparentize($_wisegreen, 0.7)};
--content_bg_info: #{transparentize($_blue, 0.5)}; --content_bg_info: #{transparentize($_blue, 0.5)};
--content_bg_danger: #{transparentize($_red, 0.5)}; --content_bg_danger: #{transparentize($_red, 0.5)};
--content_bg_backdrop: url('/images/noise.png') --content_bg_backdrop: url('/images/noise.png') #{transparentize(
#{transparentize($_brown, 0.3)}; $_brown,
0.3
)};
--content_bg_hero: url('/images/noise.png') #{transparentize($_brown, 0.6)}; --content_bg_hero: url('/images/noise.png') #{transparentize($_brown, 0.6)};
// white shades (move to --vars) // white shades (move to --vars)

View file

@ -85,8 +85,10 @@ $_ocean: #25b0bc;
--gray_90: #{transparentize(white, 0.95)}; --gray_90: #{transparentize(white, 0.95)};
// page background // page background
--page-background: 50% 50% / cover no-repeat url('/images/horizon_bg.svg') --page-background: 50% 50% / cover no-repeat url('/images/horizon_bg.svg') #{darken(
#{darken($_cold, 4%)} fixed; $_cold,
4%
)} fixed;
--page-background-top: linear-gradient( --page-background-top: linear-gradient(
#{$_accent} -150%, #{$_accent} -150%,
#{transparentize($_ocean, 0.99)} 100px, #{transparentize($_ocean, 0.99)} 100px,

View file

@ -10,7 +10,6 @@ import {
COMMENT_BLOCK_TYPES, COMMENT_BLOCK_TYPES,
ICommentBlock, ICommentBlock,
} from '~/constants/comment'; } from '~/constants/comment';
import { headerHeight } from '~/constants/dom';
import { IFile, ValueOf } from '~/types'; import { IFile, ValueOf } from '~/types';
import { CONFIG } from '~/utils/config'; import { CONFIG } from '~/utils/config';
import { import {
@ -214,14 +213,3 @@ export const sizeOf = (bytes: number): string => {
(bytes / Math.pow(1024, e)).toFixed(2) + ' ' + ' KMGTP'.charAt(e) + 'B' (bytes / Math.pow(1024, e)).toFixed(2) + ' ' + ' KMGTP'.charAt(e) + 'B'
); );
}; };
/** Tells if element is in view */
export const isFullyVisible = (element?: HTMLElement): boolean => {
if (!element) {
return false;
}
const rect = element.getBoundingClientRect();
return rect?.top > headerHeight && rect?.bottom < window.innerHeight;
};