Fix all type errors in design system (#3956)

* 📘 Fix type errors in design system

* 🔥 Remove unneeded `?`

* 🔧 Add design system to Vetur

* 📘 Improve typing of `$el`

* ♻️ Address feedback

* 📘 Type leftover `MouseEvent`

* 📘 Type `event.target` properly
This commit is contained in:
Iván Ovejero 2022-08-29 12:21:40 +02:00 committed by GitHub
parent 1e6b1b8227
commit 3ae6450f0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 153 additions and 104 deletions

View file

@ -35,8 +35,9 @@ import ElDropdown from 'element-ui/lib/dropdown';
import ElDropdownMenu from 'element-ui/lib/dropdown-menu';
import ElDropdownItem from 'element-ui/lib/dropdown-item';
import N8nIcon from '../N8nIcon';
import Vue from 'vue';
export default {
export default Vue.extend({
name: 'n8n-action-toggle',
components: {
ElDropdown,
@ -79,7 +80,7 @@ export default {
this.$emit('visible-change', value);
},
},
};
});
</script>
<style lang="scss" module>

View file

@ -53,7 +53,7 @@ export default Vue.extend({
},
},
methods: {
getColors(colors): string[] {
getColors(colors: string[]): string[] {
const style = getComputedStyle(document.body);
return colors.map((color: string) => style.getPropertyValue(color));
},

View file

@ -21,7 +21,7 @@ import Vue from 'vue';
import N8nIcon from '../N8nIcon';
import N8nText from '../N8nText';
const CALLOUT_DEFAULT_ICONS = {
const CALLOUT_DEFAULT_ICONS: { [key: string]: string } = {
info: 'info-circle',
success: 'check-circle',
warning: 'exclamation-triangle',

View file

@ -56,8 +56,8 @@ export default Vue.extend({
},
},
methods: {
onChange(e) {
this.$emit("input", e);
onChange(event: Event) {
this.$emit("input", event);
},
}
});

View file

@ -112,8 +112,8 @@ export default Vue.extend({
onButtonClick() {
this.formBus.$emit('submit');
},
onSecondaryButtonClick(e) {
this.$emit('secondaryClick', e);
onSecondaryButtonClick(event: Event) {
this.$emit('secondaryClick', event);
},
},
});

View file

@ -155,7 +155,7 @@ export default mixins(Locale).extend({
this.$emit('validate', !this.validationError);
if (this.focusInitially && this.$refs.input) {
this.$refs.input.focus();
(this.$refs.input as HTMLTextAreaElement).focus();
}
},
computed: {
@ -186,7 +186,7 @@ export default mixins(Locale).extend({
} as { [key: string]: IValidator | RuleGroup };
if (this.required) {
const error = getValidationError(this.value, validators, validators.REQUIRED as Validator);
const error = getValidationError(this.value, validators, validators.REQUIRED as IValidator);
if (error) {
return error;
}
@ -199,7 +199,7 @@ export default mixins(Locale).extend({
const error = getValidationError(
this.value,
validators,
validators[rule.name] as Validator,
validators[rule.name] as IValidator,
rule.config,
);
if (error) {
@ -235,9 +235,9 @@ export default mixins(Locale).extend({
onFocus() {
this.$emit('focus');
},
onEnter(e) {
e.stopPropagation();
e.preventDefault();
onEnter(event: Event) {
event.stopPropagation();
event.preventDefault();
this.$emit('enter');
},
},

View file

@ -29,7 +29,7 @@
<script lang="ts">
import Vue from 'vue';
import N8nFormInput from '../N8nFormInput';
import { IFormInputs } from '../../types';
import type { IFormInput } from '../../types';
import ResizeObserver from '../ResizeObserver';
export default Vue.extend({
@ -60,7 +60,7 @@ export default Vue.extend({
};
},
mounted() {
(this.inputs as IFormInputs).forEach((input: IFormInput) => {
(this.inputs as IFormInput[]).forEach((input) => {
if (input.hasOwnProperty('initialValue')) {
Vue.set(this.values, input.name, input.initialValue);
}
@ -72,7 +72,11 @@ export default Vue.extend({
},
computed: {
filteredInputs(): IFormInput[] {
return this.inputs.filter((input: IFormInput) => typeof input.shouldDisplay === 'function'? input.shouldDisplay(this.values): true);
return (this.inputs as IFormInput[]).filter(
(input) => typeof input.shouldDisplay === 'function'
? input.shouldDisplay(this.values)
: true
);
},
isReadyToSubmit(): boolean {
for (let key in this.validity) {
@ -98,7 +102,7 @@ export default Vue.extend({
onSubmit() {
this.showValidationWarnings = true;
if (this.isReadyToSubmit) {
const toSubmit = this.filteredInputs.reduce((accu, input: IFormInput) => {
const toSubmit = (this.filteredInputs as IFormInput[]).reduce<{ [key: string]: unknown }>((accu, input) => {
if (this.values[input.name]) {
accu[input.name] = this.values[input.name];
}

View file

@ -46,7 +46,7 @@ export default Vue.extend({
applied.push(this.bold? 'bold': 'regular');
return applied.map((c) => this.$style[c]);
return applied.map((c) => (this.$style as { [key: string]: string })[c]);
}
},
});

View file

@ -58,10 +58,6 @@ export default Vue.extend({
type: Boolean,
default: true,
},
circle: {
type: Boolean,
default: true,
},
},
});
</script>

View file

@ -87,22 +87,43 @@ export default Vue.extend({
},
methods: {
focus() {
if (this.$refs.innerInput.$el) {
// @ts-ignore
(this.$refs.innerInput.$el.querySelector(this.type === 'textarea' ? 'textarea' : 'input') as HTMLInputElement).focus();
}
const innerInput = this.$refs.innerInput as Vue | undefined;
if (!innerInput) return;
const inputElement = innerInput.$el.querySelector(
this.type === 'textarea' ? 'textarea' : 'input',
);
if (!inputElement) return;
inputElement.focus();
},
blur() {
if (this.$refs.innerInput.$el) {
// @ts-ignore
(this.$refs.innerInput.$el.querySelector(this.type === 'textarea' ? 'textarea' : 'input') as HTMLInputElement).blur();
}
const innerInput = this.$refs.innerInput as Vue | undefined;
if (!innerInput) return;
const inputElement = innerInput.$el.querySelector(
this.type === 'textarea' ? 'textarea' : 'input',
);
if (!inputElement) return;
inputElement.blur();
},
select() {
if (this.$refs.innerInput.$el) {
// @ts-ignore
(this.$refs.innerInput.$el.querySelector(this.type === 'textarea' ? 'textarea' : 'input') as HTMLInputElement).select();
}
const innerInput = this.$refs.innerInput as Vue | undefined;
if (!innerInput) return;
const inputElement = innerInput.$el.querySelector(
this.type === 'textarea' ? 'textarea' : 'input',
);
if (!inputElement) return;
inputElement.select();
},
},
});

View file

@ -18,8 +18,6 @@ import Vue from 'vue';
import N8nText from '../N8nText';
import N8nRoute from '../N8nRoute';
import Vue from 'vue';
export default Vue.extend({
name: 'n8n-link',
props: {

View file

@ -28,7 +28,7 @@ const markdownLink = require('markdown-it-link-attributes');
const markdownEmoji = require('markdown-it-emoji');
const markdownTasklists = require('markdown-it-task-lists');
import xss from 'xss';
import xss, { friendlyAttrValue } from 'xss';
import { escapeMarkdown } from '../../utils/markdown';
const DEFAULT_OPTIONS_MARKDOWN = {
@ -131,7 +131,7 @@ export default Vue.extend({
if (tag === 'img' && name === 'src') {
if (value.match(fileIdRegex)) {
const id = value.split('fileId:')[1];
return `src=${xss.friendlyAttrValue(imageUrls[id])}` || '';
return `src=${friendlyAttrValue(imageUrls[id])}` || '';
}
// Only allow http requests to supported image files from the `static` directory
const isImageFile = value.split('#')[0].match(/\.(jpeg|jpg|gif|png|webp)$/) !== null;
@ -162,13 +162,14 @@ export default Vue.extend({
};
},
methods: {
onClick(event) {
onClick(event: MouseEvent) {
let clickedLink = null;
if(event.target instanceof HTMLAnchorElement) {
clickedLink = event.target;
}
if(event.target.matches('a *')) {
if(event.target instanceof HTMLElement && event.target.matches('a *')) {
const parentLink = event.target.closest('a');
if(parentLink) {
clickedLink = parentLink;

View file

@ -77,24 +77,27 @@ export default Vue.extend({
},
);
},
onClick(e) {
if (e.target.localName !== 'a') return;
onClick(event: MouseEvent) {
if (!(event.target instanceof HTMLElement)) return;
if (e.target.dataset && e.target.dataset.key) {
e.stopPropagation();
e.preventDefault();
if (event.target.localName !== 'a') return;
if (e.target.dataset.key === 'show-less') {
if (event.target.dataset && event.target.dataset.key) {
event.stopPropagation();
event.preventDefault();
if (event.target.dataset.key === 'show-less') {
this.showFullContent = false;
} else if (this.canTruncate && e.target.dataset.key === 'toggle-expand') {
} else if (this.canTruncate && event.target.dataset.key === 'toggle-expand') {
this.showFullContent = !this.showFullContent;
} else {
this.$emit('action', e.target.dataset.key);
this.$emit('action', event.target.dataset.key);
}
}
},
},
});
</script>
<style lang="scss" module>

View file

@ -36,7 +36,7 @@ export default Vue.extend({
RadioButton,
},
methods: {
onClick(value) {
onClick(value: unknown) {
if (this.disabled) {
return;
}

View file

@ -112,21 +112,21 @@ export default Vue.extend({
},
methods: {
focus() {
const input = this.$refs.innerSelect;
if (input) {
input.focus();
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
if (select) {
select.focus();
}
},
blur() {
const input = this.$refs.innerSelect;
if (input) {
input.blur();
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
if (select) {
select.blur();
}
},
focusOnInput() {
const select = (this.$refs.innerSelect) as (Vue | undefined);
const select = this.$refs.innerSelect as Vue & HTMLElement | undefined;
if (select) {
const input = select.$refs.input;
const input = select.$refs.input as Vue & HTMLElement | undefined;
if (input) {
input.focus();
}

View file

@ -13,7 +13,7 @@
</template>
<script lang="ts">
const cursorMap = {
const cursorMap: { [key: string]: string } = {
right: 'ew-resize',
top: 'ns-resize',
bottom: 'ns-resize',
@ -25,7 +25,7 @@ const cursorMap = {
};
function closestNumber(value: number, divisor: number): number {
let q = parseInt(value / divisor);
let q = value / divisor;
let n1 = divisor * q;
let n2 = (value * divisor) > 0 ?
@ -37,7 +37,7 @@ function closestNumber(value: number, divisor: number): number {
return n2;
}
function getSize(delta, min, virtual, gridSize): number {
function getSize(delta: number, min: number, virtual: number, gridSize: number): number {
const target = closestNumber(virtual, gridSize);
if (target >= min && virtual > 0) {
return target;
@ -87,16 +87,19 @@ export default Vue.extend({
};
},
methods: {
resizerMove(e) {
e.preventDefault();
e.stopPropagation();
resizerMove(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
const targetResizer = e.target;
const targetResizer = event.target as { dataset: { dir: string } } | null;
if (targetResizer) {
this.dir = targetResizer.dataset.dir;
}
document.body.style.cursor = cursorMap[this.dir];
this.x = e.pageX;
this.y = e.pageY;
this.x = event.pageX;
this.y = event.pageY;
this.dWidth = 0;
this.dHeight = 0;
this.vHeight = this.height;
@ -106,27 +109,27 @@ export default Vue.extend({
window.addEventListener('mouseup', this.mouseUp);
this.$emit('resizestart');
},
mouseMove(e) {
e.preventDefault();
e.stopPropagation();
mouseMove(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
let dWidth = 0;
let dHeight = 0;
let top = false;
let left = false;
if (this.dir.includes('right')) {
dWidth = e.pageX - this.x;
dWidth = event.pageX - this.x;
}
if (this.dir.includes('left')) {
dWidth = this.x - e.pageX;
dWidth = this.x - event.pageX;
left = true;
}
if (this.dir.includes('top')) {
dHeight = this.y - e.pageY;
dHeight = this.y - event.pageY;
top = true;
}
if (this.dir.includes('bottom')) {
dHeight = e.pageY - this.y;
dHeight = event.pageY - this.y;
}
const deltaWidth = (dWidth - this.dWidth) / this.scale;
@ -144,9 +147,9 @@ export default Vue.extend({
this.dHeight = dHeight;
this.dWidth = dWidth;
},
mouseUp(e) {
e.preventDefault();
e.stopPropagation();
mouseUp(event: Event) {
event.preventDefault();
event.stopPropagation();
this.$emit('resizeend');
window.removeEventListener('mousemove', this.mouseMove);
window.removeEventListener('mouseup', this.mouseUp);

View file

@ -66,7 +66,7 @@
<script lang="ts">
import N8nInput from '../N8nInput';
import N8nMarkdown from '../N8nMarkdown';
import Resize from './Resize';
import Resize from './Resize.vue';
import N8nText from '../N8nText';
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
@ -141,13 +141,13 @@ export default mixins(Locale).extend({
}
return this.width;
},
styles() {
styles(): { height: string, width: string } {
return {
height: this.resHeight + 'px',
width: this.resWidth + 'px',
};
},
shouldShowFooter() {
shouldShowFooter(): boolean {
return this.resHeight > 100 && this.resWidth > 155;
},
},
@ -157,7 +157,7 @@ export default mixins(Locale).extend({
this.$emit('edit', true);
}
},
onInputBlur(value) {
onInputBlur() {
if (!this.isResizing) {
this.$emit('edit', false);
}
@ -165,13 +165,13 @@ export default mixins(Locale).extend({
onInput(value: string) {
this.$emit('input', value);
},
onMarkdownClick(link, event) {
onMarkdownClick(link: string, event: Event) {
this.$emit('markdown-click', link, event);
},
onResize(values) {
onResize(values: unknown[]) {
this.$emit('resize', values);
},
onResizeEnd(resizeEnd) {
onResizeEnd(resizeEnd: unknown) {
this.isResizing = false;
this.$emit('resizeend', resizeEnd);
},
@ -187,7 +187,7 @@ export default mixins(Locale).extend({
!prevMode &&
this.$refs.input
) {
const textarea = this.$refs.input;
const textarea = this.$refs.input as HTMLTextAreaElement;
if (this.defaultText === this.content) {
textarea.select();
}

View file

@ -51,12 +51,13 @@ export default Vue.extend({
N8nIcon,
},
mounted() {
const container = this.$refs.tabs;
const container = this.$refs.tabs as HTMLDivElement | undefined;
if (container) {
container.addEventListener('scroll', (e) => {
container.addEventListener('scroll', (event: Event) => {
const width = container.clientWidth;
const scrollWidth = container.scrollWidth;
this.scrollPosition = e.srcElement.scrollLeft;
// @ts-ignore
this.scrollPosition = event.srcElement.scrollLeft;
this.canScrollRight = scrollWidth - width > this.scrollPosition;
});
@ -73,13 +74,15 @@ export default Vue.extend({
}
},
destroyed() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
},
data() {
return {
scrollPosition: 0,
canScrollRight: false,
resizeObserver: null,
resizeObserver: null as ResizeObserver | null,
};
},
props: {
@ -102,13 +105,16 @@ export default Vue.extend({
this.scroll(50);
},
scroll(left: number) {
const container = this.$refs.tabs;
const container = this.$refs.tabs as (HTMLDivElement & { scrollBy: ScrollByFunction }) | undefined;
if (container) {
container.scrollBy({ left, top: 0, behavior: 'smooth' });
}
},
},
});
type ScrollByFunction = (arg: { left: number, top: number, behavior: 'smooth' | 'instant' | 'auto' }) => void;
</script>

View file

@ -54,7 +54,7 @@ export default Vue.extend({
applied.push(this.bold? 'bold': 'regular');
return applied.map((c) => this.$style[c]);
return applied.map((c) => (this.$style as { [key: string]: string })[c]);
}
},
});

View file

@ -44,7 +44,7 @@ export default Vue.extend({
},
},
methods: {
isSimple(data: unkown): boolean {
isSimple(data: unknown): boolean {
if (data === null || data === undefined) {
return true;
}
@ -59,7 +59,7 @@ export default Vue.extend({
return typeof data !== 'object';
},
getPath(key: string): string[] {
getPath(key: string): unknown[] {
if (Array.isArray(this.value)) {
return [...this.path, parseInt(key, 10)];
}

View file

@ -69,8 +69,8 @@ export default mixins(Locale).extend({
},
computed: {
fitleredUsers(): IUser[] {
return this.users
.filter((user: IUser) => {
return (this.users as IUser[])
.filter((user) => {
if (user.isPendingUser || !user.email) {
return false;
}
@ -98,6 +98,10 @@ export default mixins(Locale).extend({
return a.firstName > b.firstName? 1 : -1;
}
if (!a.email || !b.email) {
throw new Error('Expected all users to have email');
}
return a.email > b.email ? 1 : -1;
});
},

View file

@ -54,6 +54,10 @@ export default mixins(Locale).extend({
computed: {
sortedUsers(): IUser[] {
return [...(this.users as IUser[])].sort((a: IUser, b: IUser) => {
if (!a.email || !b.email) {
throw new Error('Expected all users to have email');
}
// invited users sorted by email
if (a.isPendingUser && b.isPendingUser) {
return a.email > b.email ? 1 : -1;
@ -87,7 +91,7 @@ export default mixins(Locale).extend({
},
},
methods: {
getActions(user: IUser) {
getActions(user: IUser): Array<{ label: string, value: string }> {
const DELETE = {
label: this.t('nds.usersList.deleteUser'),
value: 'delete',

View file

@ -24,7 +24,7 @@ export default Vue.extend({
},
},
},
data(): {observer: ResizeObserver | null, width: number | null} {
data(): { observer: ResizeObserver | null, bp: string } {
return {
observer: null,
bp: '',
@ -35,7 +35,9 @@ export default Vue.extend({
return;
}
const bps = [...(this.breakpoints || [])].sort((a, b) => a.width - b.width);
const unsortedBreakpoints = [...(this.breakpoints || [])] as Array<{ width: number; bp: string }>;
const bps = unsortedBreakpoints.sort((a, b) => a.width - b.width);
const observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
@ -57,7 +59,7 @@ export default Vue.extend({
this.$data.observer = observer;
if (this.$refs.root) {
observer.observe(this.$refs.root);
observer.observe(this.$refs.root as HTMLDivElement);
}
},
beforeDestroy() {

View file

@ -4,6 +4,7 @@ import * as locale from './locale';
declare module 'vue/types/vue' {
interface Vue {
$style: Record<string, string>;
t: (key: string, options?: object) => string;
}
}

View file

@ -36,3 +36,6 @@ declare module 'element-ui/lib/popover';
declare module 'element-ui/lib/transitions/collapse-transition';
declare module 'element-ui/lib/tooltip';
declare module 'element-ui/lib/input-number';
declare module 'element-ui/lib/input';
declare module 'element-ui/lib/skeleton';
declare module 'element-ui/lib/skeleton-item';

View file

@ -0,0 +1 @@
declare module 'vue2-boring-avatars';

View file

@ -6,7 +6,7 @@ export type RuleGroup = {
};
export type IValidator = {
validate: (value: string | number | boolean | null | undefined, config: any) => false | {messageKey: string, options?: any}; // tslint:disable-line:no-any
validate: (value: string | number | boolean | null | undefined, config: any) => false | {messageKey: string, options?: any} | null; // tslint:disable-line:no-any
};

View file

@ -2,7 +2,8 @@ export interface IUser {
id: string;
firstName?: string;
lastName?: string;
fullName?: string;
email?: string;
isPending: boolean;
isPendingUser: boolean;
isOwner: boolean;
}

View file

@ -1,3 +1,3 @@
module.exports = {
projects: ['./packages/editor-ui'],
projects: ['./packages/editor-ui', './packages/design-system'],
};