1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-11-09 17:50:52 +03:00

feat(editor): Add color selector to sticky node (#7453)

fixes: https://linear.app/n8n/issue/ADO-1223/feature-sticky-colors

---------

Co-authored-by: Giulio Andreini <g.andreini@gmail.com>
Co-authored-by: Mutasem <mutdmour@gmail.com>
This commit is contained in:
Ricardo Espinoza 2023-11-08 08:23:57 -05:00 committed by GitHub
parent 020042ef1a
commit 8359364536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 251 additions and 13 deletions

View File

@ -1,4 +1,6 @@
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
import { getPopper } from '../utils';
import { Interception } from 'cypress/types/net-stubbing';
const workflowPage = new WorkflowPageClass();
@ -66,6 +68,32 @@ describe('Canvas Actions', () => {
workflowPage.getters.stickies().should('have.length', 0);
});
it('change sticky color', () => {
workflowPage.actions.addSticky();
workflowPage.getters.stickies().should('have.length', 1);
workflowPage.actions.toggleColorPalette();
getPopper().should('be.visible');
workflowPage.actions.pickColor(2);
workflowPage.actions.toggleColorPalette();
getPopper().should('not.be.visible');
workflowPage.actions.saveWorkflowOnButtonClick();
cy.wait('@createWorkflow').then((interception: Interception) => {
const { request } = interception;
const color = request.body?.nodes[0]?.parameters?.color;
expect(color).to.equal(2);
});
workflowPage.getters.stickies().should('have.length', 1);
});
it('edits sticky and updates content as markdown', () => {
workflowPage.actions.addSticky();

View File

@ -126,6 +126,7 @@ export class WorkflowPage extends BasePage {
stickies: () => cy.getByTestId('sticky'),
editorTabButton: () => cy.getByTestId('radio-button-workflow'),
workflowHistoryButton: () => cy.getByTestId('workflow-history-button'),
colors: () => cy.getByTestId('color'),
};
actions = {
visit: (preventNodeViewUnload = true) => {
@ -328,6 +329,17 @@ export class WorkflowPage extends BasePage {
deleteSticky: () => {
this.getters.stickies().eq(0).realHover().find('[data-test-id="delete-sticky"]').click();
},
toggleColorPalette: () => {
this.getters
.stickies()
.eq(0)
.realHover()
.find('[data-test-id="change-sticky-color"]')
.click({ force: true });
},
pickColor: (index: number) => {
this.getters.colors().eq(1).click();
},
editSticky: (content: string) => {
this.getters.stickies().dblclick().find('textarea').clear().type(content).type('{esc}');
},

View File

@ -1,6 +1,11 @@
<template>
<div
:class="{ 'n8n-sticky': true, [$style.sticky]: true, [$style.clickable]: !isResizing }"
:class="{
'n8n-sticky': true,
[$style.sticky]: true,
[$style.clickable]: !isResizing,
[$style[`color-${backgroundColor}`]]: true,
}"
:style="styles"
@keydown.prevent
>
@ -106,6 +111,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
backgroundColor: {
value: [Number, String],
default: 1,
},
},
components: {
N8nInput,
@ -132,10 +141,12 @@ export default defineComponent({
return this.width;
},
styles(): { height: string; width: string } {
return {
const styles: { height: string; width: string } = {
height: `${this.resHeight}px`,
width: `${this.resWidth}px`,
};
return styles;
},
shouldShowFooter(): boolean {
return this.resHeight > 100 && this.resWidth > 155;
@ -189,8 +200,6 @@ export default defineComponent({
<style lang="scss" module>
.sticky {
position: absolute;
background-color: var(--color-sticky-default-background);
border: 1px solid var(--color-sticky-default-border);
border-radius: var(--border-radius-base);
}
@ -212,12 +221,6 @@ export default defineComponent({
left: 0;
bottom: 0;
position: absolute;
background: linear-gradient(
180deg,
var(--color-sticky-default-background),
#fff5d600 0.01%,
var(--color-sticky-default-background)
);
border-radius: var(--border-radius-base);
}
}
@ -227,6 +230,100 @@ export default defineComponent({
display: flex;
justify-content: flex-end;
}
.color-1 {
background-color: var(--sticky-color-1);
border: 1px solid var(--color-sticky, var(--sticky-color-1));
.wrapper::after {
background: linear-gradient(
180deg,
var(--color-sticky, var(--sticky-color-1)),
#fff5d600 0.01%,
var(--color-sticky, var(--sticky-color-1))
);
}
}
.color-2 {
background-color: var(--sticky-color-2);
border: 1px solid var(--color-sticky, var(--sticky-color-2));
.wrapper::after {
background: linear-gradient(
180deg,
var(--color-sticky, var(--sticky-color-2)),
#fff5d600 0.01%,
var(--color-sticky, var(--sticky-color-2))
);
}
}
.color-3 {
background-color: var(--sticky-color-3);
border: 1px solid var(--color-sticky, var(--sticky-color-3));
.wrapper::after {
background: linear-gradient(
180deg,
var(--color-sticky, var(--sticky-color-3)),
#fff5d600 0.01%,
var(--color-sticky, var(--sticky-color-3))
);
}
}
.color-4 {
background-color: var(--sticky-color-4);
border: 1px solid var(--color-sticky, var(--sticky-color-4));
.wrapper::after {
background: linear-gradient(
180deg,
var(--color-sticky, var(--sticky-color-4)),
#fff5d600 0.01%,
var(--color-sticky, var(--sticky-color-4))
);
}
}
.color-5 {
background-color: var(--sticky-color-5);
border: 1px solid var(--color-sticky, var(--sticky-color-5));
.wrapper::after {
background: linear-gradient(
180deg,
var(--color-sticky, var(--sticky-color-5)),
#fff5d600 0.01%,
var(--color-sticky, var(--sticky-color-5))
);
}
}
.color-6 {
background-color: var(--sticky-color-6);
border: 1px solid var(--color-sticky, var(--sticky-color-6));
.wrapper::after {
background: linear-gradient(
180deg,
var(--color-sticky, var(--sticky-color-6)),
#fff5d600 0.01%,
var(--color-sticky, var(--sticky-color-6))
);
}
}
.color-7 {
background-color: var(--sticky-color-7);
border: 1px solid var(--color-sticky, var(--sticky-color-7));
.wrapper::after {
background: linear-gradient(
180deg,
var(--color-sticky, var(--sticky-color-7)),
#fff5d600 0.01%,
var(--color-sticky, var(--sticky-color-7))
);
}
}
</style>
<style lang="scss">

View File

@ -79,6 +79,14 @@
--color-sticky-font: var(--prim-gray-740);
--color-sticky-code-background: var(--color-background-base);
--sticky-color-7: #f0f3f9;
--sticky-color-6: #e7d6ff;
--sticky-color-5: #d6ebff;
--sticky-color-4: #dcf9eb;
--sticky-color-3: #fbdadd;
--sticky-color-2: #fde9d8;
--sticky-color-1: #fff5d6;
// Expressions
--color-valid-resolvable-foreground: var(--prim-color-alt-a);
--color-valid-resolvable-background: var(--prim-color-alt-a-tint-500);

View File

@ -27,6 +27,7 @@
:height="node.parameters.height"
:width="node.parameters.width"
:scale="nodeViewScale"
:backgroundColor="node.parameters.color"
:id="node.id"
:readOnly="isReadOnly"
:defaultText="defaultText"
@ -41,7 +42,10 @@
/>
</div>
<div v-show="showActions" class="sticky-options no-select-on-click">
<div
v-show="showActions"
:class="{ 'sticky-options': true, 'no-select-on-click': true, 'force-show': forceActions }"
>
<div
v-touch:tap="deleteNode"
class="option"
@ -50,6 +54,45 @@
>
<font-awesome-icon icon="trash" />
</div>
<n8n-popover
effect="dark"
:popper-style="{ width: '208px' }"
trigger="click"
placement="top"
@show="onShowPopover"
@hide="onHidePopover"
>
<template #reference>
<div
class="option"
data-test-id="change-sticky-color"
:title="$locale.baseText('node.changeColor')"
>
<font-awesome-icon icon="palette" />
</div>
</template>
<div class="content">
<div
class="color"
data-test-id="color"
v-for="(_, index) in Array.from({ length: 7 })"
:key="index"
v-on:click="changeColor(index + 1)"
:class="`sticky-color-${index + 1}`"
:style="{
'border-width': '1px',
'border-style': 'solid',
'border-color': 'var(--color-text-dark)',
'background-color': `var(--sticky-color-${index + 1})`,
'box-shadow':
(index === 0 && node?.parameters.color === '') ||
index + 1 === node?.parameters.color
? `0 0 0 1px var(--sticky-color-${index + 1})`
: 'none',
}"
></div>
</div>
</n8n-popover>
</div>
</div>
</div>
@ -149,7 +192,10 @@ export default defineComponent({
return returnStyles;
},
showActions(): boolean {
return !(this.hideActions || this.isReadOnly || this.workflowRunning || this.isResizing);
return (
!(this.hideActions || this.isReadOnly || this.workflowRunning || this.isResizing) ||
this.forceActions
);
},
workflowRunning(): boolean {
return this.uiStore.isActionActive('workflowRunning');
@ -157,16 +203,29 @@ export default defineComponent({
},
data() {
return {
forceActions: false,
isResizing: false,
isTouchActive: false,
};
},
methods: {
onShowPopover() {
this.forceActions = true;
},
onHidePopover() {
this.forceActions = false;
},
async deleteNode() {
// Wait a tick else vue causes problems because the data is gone
await this.$nextTick();
this.$emit('removeNode', this.data.name);
},
changeColor(index: number) {
this.workflowsStore.updateNodeProperties({
name: this.name,
properties: { parameters: { ...this.node.parameters, color: index } },
});
},
onEdit(edit: boolean) {
if (edit && !this.isActive && this.node) {
this.ndvStore.activeNodeName = this.node.name;
@ -211,12 +270,13 @@ export default defineComponent({
onResizeEnd() {
this.isResizing = false;
},
setParameters(params: { content?: string; height?: number; width?: number }) {
setParameters(params: { content?: string; height?: number; width?: number; color?: string }) {
if (this.node) {
const nodeParameters = {
content: isString(params.content) ? params.content : this.node.parameters.content,
height: isNumber(params.height) ? params.height : this.node.parameters.height,
width: isNumber(params.width) ? params.width : this.node.parameters.width,
color: isString(params.color) ? params.color : this.node.parameters.color,
};
const updateInformation: IUpdateInformation = {
@ -299,6 +359,10 @@ export default defineComponent({
}
}
.force-show {
display: flex;
}
&.is-touch-device .sticky-options {
left: -25px;
width: 150px;
@ -322,4 +386,22 @@ export default defineComponent({
top: -8px;
z-index: 0;
}
.content {
display: flex;
flex-direction: row;
width: fit-content;
gap: var(--spacing-2xs);
}
.color {
width: 20px;
height: 20px;
border-radius: 50%;
border-color: var(--color-primary-shade-1);
&:hover {
cursor: pointer;
}
}
</style>

View File

@ -805,6 +805,7 @@
"node.thisIsATriggerNode": "This is a Trigger node. <a target=\"_blank\" href=\"https://docs.n8n.io/workflows/components/nodes/\">Learn more</a>",
"node.activateDeactivateNode": "Activate/Deactivate Node",
"node.deleteNode": "Delete Node",
"node.changeColor": "Change Color",
"node.disabled": "Disabled",
"node.duplicateNode": "Duplicate Node",
"node.editNode": "Edit Node",

View File

@ -94,6 +94,7 @@ import {
faMapSigns,
faMousePointer,
faNetworkWired,
faPalette,
faPause,
faPauseCircle,
faPen,
@ -253,6 +254,7 @@ export const FontAwesomePlugin: Plugin<{}> = {
addIcon(faMapSigns);
addIcon(faMousePointer);
addIcon(faNetworkWired);
addIcon(faPalette);
addIcon(faPause);
addIcon(faPauseCircle);
addIcon(faPen);

View File

@ -44,6 +44,14 @@ export class StickyNote implements INodeType {
required: true,
default: 240,
},
{
displayName: 'Color',
name: 'color',
// eslint-disable-next-line n8n-nodes-base/node-param-color-type-unused
type: 'number',
required: true,
default: 1,
},
],
};