mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-18 08:41:56 +03:00
Merge pull request #64 from toeverything/feat/frame-background-color
feat: support set frame background color
This commit is contained in:
commit
fbe9add924
@ -11,6 +11,7 @@ import { StrokeLineStyleConfig } from './stroke-line-style-config';
|
|||||||
import { Group, UnGroup } from './GroupOperation';
|
import { Group, UnGroup } from './GroupOperation';
|
||||||
import { DeleteShapes } from './DeleteOperation';
|
import { DeleteShapes } from './DeleteOperation';
|
||||||
import { Lock, Unlock } from './LockOperation';
|
import { Lock, Unlock } from './LockOperation';
|
||||||
|
import { FrameFillColorConfig } from './FrameFillColorConfig';
|
||||||
|
|
||||||
export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
||||||
const state = app.useStore();
|
const state = app.useStore();
|
||||||
@ -51,6 +52,13 @@ export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
|||||||
shapes={config.fill.selectedShapes}
|
shapes={config.fill.selectedShapes}
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
|
frameFill: config.frameFill.selectedShapes.length ? (
|
||||||
|
<FrameFillColorConfig
|
||||||
|
key="fill"
|
||||||
|
app={app}
|
||||||
|
shapes={config.frameFill.selectedShapes}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
font: config.font.selectedShapes.length ? (
|
font: config.font.selectedShapes.length ? (
|
||||||
<FontSizeConfig
|
<FontSizeConfig
|
||||||
key="font"
|
key="font"
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
import type { TldrawApp } from '@toeverything/components/board-state';
|
||||||
|
import type { TDShape } from '@toeverything/components/board-types';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
Tooltip,
|
||||||
|
IconButton,
|
||||||
|
useTheme,
|
||||||
|
} from '@toeverything/components/ui';
|
||||||
|
import {
|
||||||
|
ShapeColorNoneIcon,
|
||||||
|
ShapeColorDuotoneIcon,
|
||||||
|
} from '@toeverything/components/icons';
|
||||||
|
import { countBy, maxBy } from '@toeverything/utils';
|
||||||
|
import { getShapeIds } from './utils';
|
||||||
|
import { Palette } from '../palette';
|
||||||
|
|
||||||
|
interface BorderColorConfigProps {
|
||||||
|
app: TldrawApp;
|
||||||
|
shapes: TDShape[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColorType = 'none' | string;
|
||||||
|
|
||||||
|
const _colors: ColorType[] = [
|
||||||
|
'rgba(255, 133, 137, 0.5)',
|
||||||
|
'rgba(255, 159, 101, 0.5)',
|
||||||
|
'rgba(255, 251, 69, 0.5)',
|
||||||
|
'rgba(64, 255, 138, 0.5)',
|
||||||
|
'rgba(26, 252, 255, 0.5)',
|
||||||
|
'rgba(198, 156, 255, 0.5)',
|
||||||
|
'rgba(255, 143, 224, 0.5)',
|
||||||
|
'rgba(152, 172, 189, 0.5)',
|
||||||
|
'rgba(216, 226, 248, 0.5)',
|
||||||
|
];
|
||||||
|
|
||||||
|
const _getIconRenderColor = (shapes: TDShape[]) => {
|
||||||
|
const counted = countBy(shapes, shape => shape.style.fill);
|
||||||
|
const max = maxBy(Object.entries(counted), ([c, n]) => n);
|
||||||
|
return max[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FrameFillColorConfig: FC<BorderColorConfigProps> = ({
|
||||||
|
app,
|
||||||
|
shapes,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const setFillColor = (color: ColorType) => {
|
||||||
|
app.style(
|
||||||
|
{ fill: color, isFilled: color !== 'none' },
|
||||||
|
getShapeIds(shapes)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconColor = _getIconRenderColor(shapes);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
trigger="hover"
|
||||||
|
placement="bottom-start"
|
||||||
|
content={
|
||||||
|
<Palette
|
||||||
|
colors={_colors}
|
||||||
|
selected={iconColor}
|
||||||
|
onSelect={setFillColor}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Tooltip content="Frame Background Color" placement="top-start">
|
||||||
|
<IconButton>
|
||||||
|
{iconColor === 'none' ? (
|
||||||
|
<ShapeColorNoneIcon />
|
||||||
|
) : (
|
||||||
|
<ShapeColorDuotoneIcon
|
||||||
|
style={{
|
||||||
|
color: iconColor,
|
||||||
|
border:
|
||||||
|
iconColor === '#FFFFFF'
|
||||||
|
? `1px solid ${theme.affine.palette.tagHover}`
|
||||||
|
: 0,
|
||||||
|
borderRadius: '5px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
@ -7,6 +7,7 @@ interface Config {
|
|||||||
type:
|
type:
|
||||||
| 'stroke'
|
| 'stroke'
|
||||||
| 'fill'
|
| 'fill'
|
||||||
|
| 'frameFill'
|
||||||
| 'font'
|
| 'font'
|
||||||
| 'group'
|
| 'group'
|
||||||
| 'ungroup'
|
| 'ungroup'
|
||||||
@ -22,6 +23,10 @@ const _createInitConfig = (): Record<Config['type'], Config> => {
|
|||||||
type: 'fill',
|
type: 'fill',
|
||||||
selectedShapes: [],
|
selectedShapes: [],
|
||||||
},
|
},
|
||||||
|
frameFill: {
|
||||||
|
type: 'frameFill',
|
||||||
|
selectedShapes: [],
|
||||||
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
type: 'stroke',
|
type: 'stroke',
|
||||||
selectedShapes: [],
|
selectedShapes: [],
|
||||||
@ -64,6 +69,7 @@ const _isSupportStroke = (shape: TDShape): boolean => {
|
|||||||
TDShapeType.Pencil,
|
TDShapeType.Pencil,
|
||||||
TDShapeType.Laser,
|
TDShapeType.Laser,
|
||||||
TDShapeType.Highlight,
|
TDShapeType.Highlight,
|
||||||
|
TDShapeType.Draw,
|
||||||
TDShapeType.Arrow,
|
TDShapeType.Arrow,
|
||||||
TDShapeType.Line,
|
TDShapeType.Line,
|
||||||
].some(type => type === shape.type);
|
].some(type => type === shape.type);
|
||||||
@ -91,6 +97,10 @@ const _isSupportFont = (shape: TDShape): boolean => {
|
|||||||
].some(type => type === shape.type);
|
].some(type => type === shape.type);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _isSupportFrameFill = (shape: TDShape): boolean => {
|
||||||
|
return shape.type === TDShapeType.Frame;
|
||||||
|
};
|
||||||
|
|
||||||
export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
||||||
const state = app.useStore();
|
const state = app.useStore();
|
||||||
const selectedShapes = TLDR.get_selected_shapes(state, app.currentPageId);
|
const selectedShapes = TLDR.get_selected_shapes(state, app.currentPageId);
|
||||||
@ -105,6 +115,9 @@ export const useConfig = (app: TldrawApp): Record<Config['type'], Config> => {
|
|||||||
if (_isSupportFont(cur)) {
|
if (_isSupportFont(cur)) {
|
||||||
acc.font.selectedShapes.push(cur);
|
acc.font.selectedShapes.push(cur);
|
||||||
}
|
}
|
||||||
|
if (_isSupportFrameFill(cur)) {
|
||||||
|
acc.frameFill.selectedShapes.push(cur);
|
||||||
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
_createInitConfig()
|
_createInitConfig()
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import * as React from 'react';
|
/* eslint-disable no-restricted-syntax */
|
||||||
import { Utils, SVGContainer } from '@tldraw/core';
|
import { Utils, SVGContainer } from '@tldraw/core';
|
||||||
import {
|
import {
|
||||||
FrameShape,
|
FrameShape,
|
||||||
DashStyle,
|
|
||||||
TDShapeType,
|
TDShapeType,
|
||||||
TDMeta,
|
TDMeta,
|
||||||
GHOSTED_OPACITY,
|
|
||||||
LABEL_POINT,
|
|
||||||
} from '@toeverything/components/board-types';
|
} from '@toeverything/components/board-types';
|
||||||
import { TDShapeUtil } from '../TDShapeUtil';
|
import { TDShapeUtil } from '../TDShapeUtil';
|
||||||
import {
|
import {
|
||||||
@ -14,14 +11,13 @@ import {
|
|||||||
getShapeStyle,
|
getShapeStyle,
|
||||||
getBoundsRectangle,
|
getBoundsRectangle,
|
||||||
transformRectangle,
|
transformRectangle,
|
||||||
getFontStyle,
|
|
||||||
transformSingleRectangle,
|
transformSingleRectangle,
|
||||||
} from '../shared';
|
} from '../shared';
|
||||||
import { DrawFrame } from './components/draw-frame';
|
import { Frame } from './components/Frame';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
|
|
||||||
type T = FrameShape;
|
type T = FrameShape;
|
||||||
type E = HTMLDivElement;
|
type E = SVGSVGElement;
|
||||||
|
|
||||||
export class FrameUtil extends TDShapeUtil<T, E> {
|
export class FrameUtil extends TDShapeUtil<T, E> {
|
||||||
type = TDShapeType.Frame as const;
|
type = TDShapeType.Frame as const;
|
||||||
@ -56,10 +52,7 @@ export class FrameUtil extends TDShapeUtil<T, E> {
|
|||||||
(
|
(
|
||||||
{
|
{
|
||||||
shape,
|
shape,
|
||||||
isEditing,
|
|
||||||
isBinding,
|
|
||||||
isSelected,
|
isSelected,
|
||||||
isGhost,
|
|
||||||
meta,
|
meta,
|
||||||
bounds,
|
bounds,
|
||||||
events,
|
events,
|
||||||
@ -70,21 +63,20 @@ export class FrameUtil extends TDShapeUtil<T, E> {
|
|||||||
) => {
|
) => {
|
||||||
const { id, size, style } = shape;
|
const { id, size, style } = shape;
|
||||||
return (
|
return (
|
||||||
<FullWrapper ref={ref} {...events}>
|
<SVGContainer
|
||||||
<SVGContainer
|
ref={ref}
|
||||||
id={shape.id + '_svg'}
|
{...events}
|
||||||
opacity={1}
|
id={shape.id + '_svg'}
|
||||||
fill={'#fff'}
|
opacity={1}
|
||||||
>
|
>
|
||||||
<DrawFrame
|
<Frame
|
||||||
id={id}
|
id={id}
|
||||||
style={style}
|
style={style}
|
||||||
size={size}
|
size={size}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
isDarkMode={meta.isDarkMode}
|
isDarkMode={meta.isDarkMode}
|
||||||
/>
|
/>
|
||||||
</SVGContainer>
|
</SVGContainer>
|
||||||
</FullWrapper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -121,27 +113,9 @@ export class FrameUtil extends TDShapeUtil<T, E> {
|
|||||||
override transform = transformRectangle;
|
override transform = transformRectangle;
|
||||||
|
|
||||||
override transformSingle = transformSingleRectangle;
|
override transformSingle = transformSingleRectangle;
|
||||||
|
|
||||||
override hitTestPoint = (shape: T, point: number[]): boolean => {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
override hitTestLineSegment = (
|
|
||||||
shape: T,
|
|
||||||
A: number[],
|
|
||||||
B: number[]
|
|
||||||
): boolean => {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FullWrapper = styled('div')({
|
const FullWrapper = styled('div')({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
'.tl-fill-hitarea': {
|
|
||||||
fill: '#F7F9FF',
|
|
||||||
},
|
|
||||||
'.tl-stroke-hitarea': {
|
|
||||||
fill: '#F7F9FF',
|
|
||||||
},
|
|
||||||
});
|
});
|
@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { BINDING_DISTANCE } from '@toeverything/components/board-types';
|
||||||
|
import type { ShapeStyles } from '@toeverything/components/board-types';
|
||||||
|
import { getShapeStyle } from '../../shared';
|
||||||
|
|
||||||
|
interface RectangleSvgProps {
|
||||||
|
id: string;
|
||||||
|
style: ShapeStyles;
|
||||||
|
isSelected: boolean;
|
||||||
|
size: number[];
|
||||||
|
isDarkMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Frame = React.memo(function DashedRectangle({
|
||||||
|
id,
|
||||||
|
style,
|
||||||
|
size,
|
||||||
|
isSelected,
|
||||||
|
isDarkMode,
|
||||||
|
}: RectangleSvgProps) {
|
||||||
|
const { strokeWidth, fill } = getShapeStyle(style, isDarkMode);
|
||||||
|
|
||||||
|
const _fill = fill && fill !== 'none' ? fill : '#F7F9FF';
|
||||||
|
|
||||||
|
const sw = 1 + strokeWidth * 1.618;
|
||||||
|
|
||||||
|
const w = Math.max(0, size[0] - sw / 2);
|
||||||
|
const h = Math.max(0, size[1] - sw / 2);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<rect
|
||||||
|
className={
|
||||||
|
isSelected || style.isFilled
|
||||||
|
? 'tl-fill-hitarea'
|
||||||
|
: 'tl-stroke-hitarea'
|
||||||
|
}
|
||||||
|
x={sw / 2}
|
||||||
|
y={sw / 2}
|
||||||
|
width={w}
|
||||||
|
height={h}
|
||||||
|
strokeWidth={BINDING_DISTANCE}
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x={sw / 2}
|
||||||
|
y={sw / 2}
|
||||||
|
width={w}
|
||||||
|
height={h}
|
||||||
|
fill={_fill}
|
||||||
|
pointerEvents="none"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -1,42 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { BINDING_DISTANCE } from '@toeverything/components/board-types';
|
|
||||||
import type { ShapeStyles } from '@toeverything/components/board-types';
|
|
||||||
import { getShapeStyle } from '../../shared';
|
|
||||||
|
|
||||||
interface RectangleSvgProps {
|
|
||||||
id: string;
|
|
||||||
style: ShapeStyles;
|
|
||||||
isSelected: boolean;
|
|
||||||
size: number[];
|
|
||||||
isDarkMode: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DrawFrame = React.memo(function DashedRectangle({
|
|
||||||
id,
|
|
||||||
style,
|
|
||||||
size,
|
|
||||||
isSelected,
|
|
||||||
isDarkMode,
|
|
||||||
}: RectangleSvgProps) {
|
|
||||||
const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
|
|
||||||
|
|
||||||
const sw = 1 + strokeWidth * 1.618;
|
|
||||||
|
|
||||||
const w = Math.max(0, size[0] - sw / 2);
|
|
||||||
const h = Math.max(0, size[1] - sw / 2);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<rect
|
|
||||||
className={
|
|
||||||
isSelected || style.isFilled
|
|
||||||
? 'tl-fill-hitarea'
|
|
||||||
: 'tl-stroke-hitarea'
|
|
||||||
}
|
|
||||||
x={sw / 2}
|
|
||||||
y={sw / 2}
|
|
||||||
width={w}
|
|
||||||
height={h}
|
|
||||||
strokeWidth={BINDING_DISTANCE}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
@ -1 +1 @@
|
|||||||
export * from './frame-util';
|
export * from './FrameUtil';
|
||||||
|
Loading…
Reference in New Issue
Block a user