import '../buttons/tool-icon-button.js';
import '../panel/color-panel.js';
import '../buttons/menu-button.js';
import { WithDisposable } from '@blockeditor/lit';
import type { Page } from '@blockeditor/store';
import { css, html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import {
ConnectorEndpointNoneIcon,
CurveLineIcon,
DashLineIcon,
ElbowedLineIcon,
FlipDirectionIcon,
FrontEndpointArrowIcon,
FrontEndpointCircleIcon,
FrontEndpointDiamondIcon,
FrontEndpointTriangleIcon,
GeneralStyleIcon,
LineStyleIcon,
RearEndpointArrowIcon,
RearEndpointCircleIcon,
RearEndpointDiamondIcon,
RearEndpointTriangleIcon,
ScribbledStyleIcon,
SmallArrowDownIcon,
StraightLineIcon,
} from '../../../../_common/icons/index.js';
import type { CssVariableName } from '../../../../_common/theme/css-variables.js';
import { LineWidth } from '../../../../_common/types.js';
import { countBy, maxBy } from '../../../../_common/utils/iterable.js';
import {
type ConnectorElementModel,
ConnectorEndpoint,
ConnectorEndpointStyle,
ConnectorMode,
DEFAULT_FRONT_END_POINT_STYLE,
DEFAULT_REAR_END_POINT_STYLE,
StrokeStyle,
} from '../../../../surface-block/index.js';
import type { SurfaceBlockComponent } from '../../../../surface-block/surface-block.js';
import type { EdgelessPageBlockComponent } from '../../edgeless-page-block.js';
import type { LineStyleButtonProps } from '../buttons/line-style-button.js';
import {
type ColorEvent,
ColorUnit,
GET_DEFAULT_LINE_COLOR,
} from '../panel/color-panel.js';
import type { LineWidthEvent } from '../panel/line-width-panel.js';
function getMostCommonColor(
elements: ConnectorElementModel[]
): CssVariableName {
const colors = countBy(elements, (ele: ConnectorElementModel) => ele.stroke);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as CssVariableName) : GET_DEFAULT_LINE_COLOR();
}
function getMostCommonMode(
elements: ConnectorElementModel[]
): ConnectorMode | null {
const modes = countBy(elements, (ele: ConnectorElementModel) => ele.mode);
const max = maxBy(Object.entries(modes), ([_k, count]) => count);
return max ? (Number(max[0]) as ConnectorMode) : null;
}
function getMostCommonLineWidth(elements: ConnectorElementModel[]): LineWidth {
const sizes = countBy(elements, (ele: ConnectorElementModel) => {
return ele.strokeWidth;
});
const max = maxBy(Object.entries(sizes), ([_k, count]) => count);
return max ? (Number(max[0]) as LineWidth) : LineWidth.LINE_WIDTH_FOUR;
}
export function getMostCommonLineStyle(
elements: ConnectorElementModel[]
): LineStyleButtonProps['mode'] | null {
const sizes = countBy(elements, (ele: ConnectorElementModel) => {
switch (ele.strokeStyle) {
case 'solid': {
return 'solid';
}
case 'dash': {
return 'dash';
}
case 'none': {
return 'none';
}
}
});
const max = maxBy(Object.entries(sizes), ([_k, count]) => count);
return max ? (max[0] as LineStyleButtonProps['mode']) : null;
}
function getMostCommonRough(elements: ConnectorElementModel[]): boolean {
const { trueCount, falseCount } = elements.reduce(
(counts, ele) => {
if (ele.rough) {
counts.trueCount++;
} else {
counts.falseCount++;
}
return counts;
},
{ trueCount: 0, falseCount: 0 }
);
return trueCount > falseCount;
}
function getMostCommonEndpointStyle(
elements: ConnectorElementModel[],
end: ConnectorEndpoint
): ConnectorEndpointStyle | null {
const modes = countBy(elements, (ele: ConnectorElementModel) =>
end === ConnectorEndpoint.Front
? ele.frontEndpointStyle
: ele.rearEndpointStyle
);
const max = maxBy(Object.entries(modes), ([_k, count]) => count);
return max ? (max[0] as ConnectorEndpointStyle) : null;
}
@customElement('edgeless-change-connector-button')
export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
static override styles = [
css`
:host {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
color: var(--affine-text-primary-color);
fill: currentColor;
}
menu-divider {
height: 24px;
}
.connector-style-sub-menu {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
`,
];
@property({ attribute: false })
edgeless!: EdgelessPageBlockComponent;
@property({ attribute: false })
elements: ConnectorElementModel[] = [];
@property({ attribute: false })
page!: Page;
@property({ attribute: false })
surface!: SurfaceBlockComponent;
get service() {
return this.surface.edgeless.service;
}
private _setConnectorMode(mode: ConnectorMode) {
this.page.captureSync();
this.elements.forEach(element => {
if (element.mode !== mode) {
this.service.updateElement(element.id, {
mode,
});
}
});
}
private _setConnectorRough(rough: boolean) {
this.page.captureSync();
this.elements.forEach(element => {
if (element.rough !== rough) {
this.service.updateElement(element.id, {
rough,
});
}
});
}
private _setConnectorColor(stroke: CssVariableName) {
this.page.captureSync();
let shouldUpdate = false;
this.elements.forEach(element => {
if (element.stroke !== stroke) {
shouldUpdate = true;
this.service.updateElement(element.id, {
stroke,
});
}
});
if (shouldUpdate) this.requestUpdate();
}
private _setConnectorStrokeWidth(strokeWidth: number) {
this.elements.forEach(ele => {
this.service.updateElement(ele.id, {
strokeWidth,
});
});
}
private _setConnectorStrokeStyle(strokeStyle: StrokeStyle) {
this.elements.forEach(ele => {
this.service.updateElement(ele.id, {
strokeStyle,
});
});
}
private _setConnectorEndpointStyle(
end: ConnectorEndpoint,
style: ConnectorEndpointStyle
) {
this.elements.forEach(ele => {
if (end === ConnectorEndpoint.Front) {
this.service.updateElement(ele.id, {
frontEndpointStyle: style,
});
} else {
this.service.updateElement(ele.id, {
rearEndpointStyle: style,
});
}
});
}
private _flipEndpointStyle(
frontEndpointStyle: ConnectorEndpointStyle,
rearEndpointStyle: ConnectorEndpointStyle
) {
if (frontEndpointStyle === rearEndpointStyle) return;
this.elements.forEach(ele => {
this.service.updateElement(ele.id, {
frontEndpointStyle: rearEndpointStyle,
rearEndpointStyle: frontEndpointStyle,
});
});
}
private _getEndpointIcon(
end: ConnectorEndpoint,
style: ConnectorEndpointStyle
) {
switch (style) {
case ConnectorEndpointStyle.None: {
return ConnectorEndpointNoneIcon;
}
case ConnectorEndpointStyle.Arrow: {
return end === ConnectorEndpoint.Front
? FrontEndpointArrowIcon
: RearEndpointArrowIcon;
}
case ConnectorEndpointStyle.Triangle: {
return end === ConnectorEndpoint.Front
? FrontEndpointTriangleIcon
: RearEndpointTriangleIcon;
}
case ConnectorEndpointStyle.Circle: {
return end === ConnectorEndpoint.Front
? FrontEndpointCircleIcon
: RearEndpointCircleIcon;
}
case ConnectorEndpointStyle.Diamond: {
return end === ConnectorEndpoint.Front
? FrontEndpointDiamondIcon
: RearEndpointDiamondIcon;
}
}
}
override render() {
const selectedColor = getMostCommonColor(this.elements);
const selectedMode = getMostCommonMode(this.elements);
const selectedLineSize =
getMostCommonLineWidth(this.elements) ?? LineWidth.LINE_WIDTH_FOUR;
const selectedRough = getMostCommonRough(this.elements);
const selectedLineStyle = getMostCommonLineStyle(this.elements);
const selectedStartPointStyle =
getMostCommonEndpointStyle(this.elements, ConnectorEndpoint.Front) ??
DEFAULT_FRONT_END_POINT_STYLE;
const selectedEndPointStyle =
getMostCommonEndpointStyle(this.elements, ConnectorEndpoint.Rear) ??
DEFAULT_REAR_END_POINT_STYLE;
return html`