Skip to content

Commit

Permalink
Improve opacity for blending.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhin committed Oct 30, 2023
1 parent 9470203 commit b3ea959
Show file tree
Hide file tree
Showing 14 changed files with 255 additions and 57 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"test": "pnpm run /^test:/"
},
"dependencies": {
"@figma/plugin-typings": "^1.77.0",
"@figma/plugin-typings": "^1.79.0",
"@floating-ui/react": "^0.25.2",
"@nanostores/react": "^0.7.1",
"@testing-library/react": "^14.0.0",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/api/services/figma/nodes/has-only-valid-blend-modes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// import { type FigmaNode, type FigmaPaint } from '~types/figma.ts';
// import { isEmpty, notEmpty } from '~utils/not-empty.ts';
//
// // PLUS_LIGHTER is LINEAR_DODGE
// // PLUS_DARKER is LINEAR_BURN
// const unprocessedBlendModes = ['LINEAR_BURN', 'LINEAR_DODGE'];
//
// const isVisibleSolidFill = (fill: FigmaPaint): boolean =>
// fill.visible === true &&
// (notEmpty(fill.opacity) ? fill.opacity > 0 : true) &&
// fill.type === 'SOLID';
//
// const hasValidBlendMode = (fill: FigmaPaint): boolean => {
// if (isEmpty(fill.blendMode)) return true;
//
// return !unprocessedBlendModes.includes(fill.blendMode);
// };
//
// export const hasOnlyValidBlendModes = (nodes: FigmaNode[]): boolean =>
// nodes.every(
// (node) =>
// node.fills
// .filter((fill) => isVisibleSolidFill(fill))
// .every(hasValidBlendMode) &&
// !unprocessedBlendModes.includes(node.blendMode)
// );
2 changes: 2 additions & 0 deletions src/api/services/figma/nodes/is-valid-for-background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { getActualNodeFill } from '~utils/get-actual-node-fill.ts';
import { getActualNode } from '~utils/get-actual-node.ts';
import { isEmpty } from '~utils/not-empty.ts';

// TODO: Improve if there are any non solid visible fills with opacity.

export const isValidForBackground = (nodes: FigmaNode[]): boolean => {
const actualNode = getActualNode(nodes);

Expand Down
2 changes: 2 additions & 0 deletions src/api/services/figma/nodes/is-valid-for-selection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { getActualNodeFill } from '~utils/get-actual-node-fill.ts';
import { notEmpty } from '~utils/not-empty.ts';

// TODO: Improve if there are any non solid visible fills with opacity.

export const isValidForSelection = (node: SceneNode): boolean => {
if (!node.visible) {
return false;
Expand Down
51 changes: 39 additions & 12 deletions src/api/services/payload/build-general-selection-payload.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import { getIntersectingNodes } from '~api/services/figma/intersections/get-intersecting-nodes.ts';
import { createFigmaNode } from '~api/services/figma/nodes/create-figma-node.ts';
// import { hasOnlyValidBlendModes } from '~api/services/figma/nodes/has-only-valid-blend-modes.ts';
import { isValidForBackground } from '~api/services/figma/nodes/is-valid-for-background.ts';
import { isValidForSelection } from '~api/services/figma/nodes/is-valid-for-selection.ts';
import {
type SelectionChangeEvent,
SelectionMessageTypes,
} from '~types/messages.ts';
import { isEmpty, notEmpty } from '~utils/not-empty.ts';
import { type SelectedNodes } from '~types/selection.ts';
import { notEmpty } from '~utils/not-empty.ts';

enum PairState {
InvalidBackground = 'Invalid background',
InvalidBlendMode = 'Has invalid blend mode',
}

const isSelectedNodes = (
pair: PairState | SelectedNodes
): pair is SelectedNodes => {
return notEmpty((pair as SelectedNodes).intersectingNodes);
};

export const buildGeneralSelectionPayload = (
selection: readonly SceneNode[]
Expand All @@ -15,31 +28,45 @@ export const buildGeneralSelectionPayload = (
.filter(isValidForSelection)
.map((selectedNode) => {
const intersectingNodes = getIntersectingNodes(selectedNode);
const selectedFigmaNode = createFigmaNode(selectedNode);

// if (!hasOnlyValidBlendModes([selectedFigmaNode, ...intersectingNodes])) {
// return PairState.InvalidBlendMode;
// }

if (isValidForBackground(intersectingNodes)) {
return {
intersectingNodes: getIntersectingNodes(selectedNode),
selectedNode: [createFigmaNode(selectedNode)],
selectedNodeWithIntersectingNodes: [selectedFigmaNode],
};
} else {
return null;
return PairState.InvalidBackground;
}
});

const isSingleInvalidBackground =
selectedNodePairs.some(isEmpty) && selectedNodePairs.length === 1;
selectedNodePairs.some((pair) => pair === PairState.InvalidBackground) &&
selectedNodePairs.length === 1;
const areAllInvalidBackgrounds =
selectedNodePairs.length > 1 && selectedNodePairs.every(isEmpty);
selectedNodePairs.length > 1 &&
selectedNodePairs.every((pair) => pair === PairState.InvalidBackground);

if (isSingleInvalidBackground || areAllInvalidBackgrounds) {
return {
colorSpace: figma.root.documentColorProfile,
text: SelectionMessageTypes.invalidBackground,
};
}

const invalidBackground =
isSingleInvalidBackground || areAllInvalidBackgrounds;
// if (selectedNodePairs.some((pair) => pair === PairState.InvalidBlendMode)) {
// return {
// colorSpace: figma.root.documentColorProfile,
// text: SelectionMessageTypes.unprocessedBlendModes,
// };
// }

return {
colorSpace: figma.root.documentColorProfile,
...(invalidBackground
? { text: SelectionMessageTypes.invalidBackground }
: {
selectedNodePairs: selectedNodePairs.filter(notEmpty),
}),
selectedNodePairs: selectedNodePairs.filter(isSelectedNodes),
};
};
17 changes: 14 additions & 3 deletions src/api/services/payload/build-pair-selection-payload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { areNodesIntersecting } from '~api/services/figma/intersections/are-nodes-intersecting.ts';
import { getIntersectingNodes } from '~api/services/figma/intersections/get-intersecting-nodes.ts';
import { createFigmaNode } from '~api/services/figma/nodes/create-figma-node.ts';
// import { hasOnlyValidBlendModes } from '~api/services/figma/nodes/has-only-valid-blend-modes.ts';
import { isValidForBackground } from '~api/services/figma/nodes/is-valid-for-background.ts';
import { isValidForSelection } from '~api/services/figma/nodes/is-valid-for-selection.ts';
import { sortNodesByLayers } from '~api/services/figma/nodes/sort-nodes-by-layers.ts';
Expand Down Expand Up @@ -39,6 +40,13 @@ export const buildPairSelectionPayload = (
};
}

// if (!hasOnlyValidBlendModes([bg, fg])) {
// return {
// colorSpace: figma.root.documentColorProfile,
// text: SelectionMessageTypes.unprocessedBlendModes,
// };
// }

const fgSceneNode = fg.id === firstFigmaNode.id ? firstNode : secondNode;
const bgSceneNode = bg.id === firstFigmaNode.id ? firstNode : secondNode;

Expand All @@ -48,13 +56,13 @@ export const buildPairSelectionPayload = (
selectedNodePairs: [],
};

if (areNodesIntersecting(firstNode, secondNode)) {
if (areNodesIntersecting(bgSceneNode, fgSceneNode)) {
return {
colorSpace: figma.root.documentColorProfile,
selectedNodePairs: [
{
intersectingNodes: getIntersectingNodes(fgSceneNode),
selectedNode: [fg],
selectedNodeWithIntersectingNodes: [fg],
},
],
};
Expand All @@ -64,7 +72,10 @@ export const buildPairSelectionPayload = (
selectedNodePairs: [
{
intersectingNodes: [bg, ...getIntersectingNodes(bgSceneNode)],
selectedNode: [fg, ...getIntersectingNodes(fgSceneNode)],
selectedNodeWithIntersectingNodes: [
fg,
...getIntersectingNodes(fgSceneNode),
],
},
],
};
Expand Down
1 change: 1 addition & 0 deletions src/types/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface Message<T> {

export enum SelectionMessageTypes {
invalidBackground = 'invalidBackground',
unprocessedBlendModes = 'unprocessedBlendModes',
}

export interface SelectionChangePayload {
Expand Down
2 changes: 1 addition & 1 deletion src/types/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { type FigmaNode } from './figma.ts';

export interface SelectedNodes {
intersectingNodes: FigmaNode[];
selectedNode: FigmaNode[];
selectedNodeWithIntersectingNodes: FigmaNode[];
}
7 changes: 7 additions & 0 deletions src/ui/components/AppContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { EmptySelectionMessage } from '~ui/components/EmptySelectionMessage.tsx'
import { InvalidBackgroundSelectionMessage } from '~ui/components/InvalidBackgroundSelectionMessage.tsx';
import { Selection } from '~ui/components/Selection.tsx';
import { SelectionsList } from '~ui/components/SelectionsList.tsx';
import { UnprocessedBlendModesSelectionMessage } from '~ui/components/UnprocessedBlendModesSelectionMessage.tsx';
import {
$contrastConclusion,
$isEmptySelection,
$isInvalidBackground,
$isMultiSelection,
$isUnprocessedBlendModes,
} from '~ui/stores/selected-nodes.ts';
import { isEmpty } from '~utils/not-empty.ts';
import { type ReactElement } from 'react';
Expand All @@ -16,12 +18,17 @@ export const AppContent = (): ReactElement => {
const isInvalidBackground = useStore($isInvalidBackground);
const isEmptySelection = useStore($isEmptySelection);
const isMultiSelection = useStore($isMultiSelection);
const isUnprocessedBlendModes = useStore($isUnprocessedBlendModes);
const contrastConclusion = useStore($contrastConclusion);

if (isInvalidBackground) {
return <InvalidBackgroundSelectionMessage />;
}

if (isUnprocessedBlendModes) {
return <UnprocessedBlendModesSelectionMessage />;
}

if (isEmptySelection) {
return <EmptySelectionMessage />;
}
Expand Down
15 changes: 15 additions & 0 deletions src/ui/components/UnprocessedBlendModesSelectionMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import layersImage from '~ui/assets/[email protected]';
import { type ReactElement } from 'react';

export const UnprocessedBlendModesSelectionMessage = (): ReactElement => {
return (
<p
style={{
backgroundImage: `url(${layersImage})`,
}}
className="mx-auto flex h-[200px] w-[250px] select-none items-end justify-center bg-[length:180px_180px] bg-center bg-no-repeat pt-2 text-center font-martianMono text-xxs text-secondary-75"
>
The blending modes Plus Lighter and Plus Darker are not supported
</p>
);
};
Loading

0 comments on commit b3ea959

Please sign in to comment.