implement versions modal

This commit is contained in:
Mutasem 2021-07-01 15:59:06 +02:00
parent efbee533d1
commit df32a7bbb6
16 changed files with 362 additions and 7 deletions

View file

@ -25,6 +25,7 @@
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"timeago.js": "^4.0.2",
"v-click-outside": "^3.1.2"
},
"devDependencies": {

View file

@ -546,6 +546,34 @@ export interface ITagRow {
delete?: boolean;
}
export interface IVersion {
name: string;
nodes: Array<{
name: string;
displayName: string;
icon: string;
iconData: {
type: string;
icon?: string;
fileBuffer?: string;
};
}>;
createdAt: string;
description: string;
documentationUrl: string;
hasBreakingChange: boolean;
hasBugFixes: boolean;
hasCoreChanges: boolean;
hasNewNodes: boolean;
hasNodeEnhancements: boolean;
hasSecurityFix: boolean;
hasSecurityIssue: boolean;
isAvailableOnCloud: boolean;
isStable: boolean;
securityIssueFixVersion: string;
showReleaseNotification: boolean;
}
export interface IRootState {
activeExecutions: IExecutionsCurrentSummaryExtended[];
activeWorkflows: string[];
@ -604,6 +632,11 @@ export interface IUiState {
isPageLoading: boolean;
}
export interface IVersionsState {
nextVersions: IVersion[];
currentVersion: IVersion | undefined;
}
export interface IWorkflowsState {
}

View file

@ -0,0 +1,7 @@
import { IVersion } from '@/Interface';
import { get } from './helpers';
import { VERSIONS_BASE_URL } from '@/constants';
export async function getNextVersions(version: string): Promise<IVersion[]> {
return await get(VERSIONS_BASE_URL, version);
}

View file

@ -0,0 +1,36 @@
<template>
<SlideTransition>
<div>
<slot></slot>
</div>
</SlideTransition>
</template>
<script lang="ts">
import Vue from "vue";
import SlideTransition from "./transitions/SlideTransition.vue";
export default Vue.extend({
name: "Modal",
props: ['name'],
components: {
SlideTransition,
},
computed: {
},
});
</script>
<style lang="scss" scoped>
/deep/ *, *:before, *:after {
box-sizing: border-box;
}
div {
position: fixed;
width: 480px;
height: 100%;
background-color: $--drawer-background-color;
z-index: 200;
}
</style>

View file

@ -128,9 +128,9 @@
<MenuItemsIterator :items="sidebarMenuBottomItems" :root="true"/>
<div class="foot-menu-items">
<el-menu-item index="updates" class="updates">
<el-menu-item index="updates" class="updates" v-if="hasVersionUpdates" @click="openVersionsModal">
<i><font-awesome-icon icon="gift"/></i>
<span slot="title" class="item-title-root">Updates available</span>
<span slot="title" class="item-title-root">{{nextVersions.length}} update{{nextVersions.length > 1? 's': ''}} available</span>
</el-menu-item>
</div>
</el-menu>
@ -238,6 +238,10 @@ export default mixins(
...mapGetters('ui', {
isCollapsed: 'sidebarMenuCollapsed',
}),
...mapGetters('versions', [
'hasVersionUpdates',
'nextVersions',
]),
exeuctionId (): string | undefined {
return this.$route.params.id;
},
@ -317,6 +321,9 @@ export default mixins(
openTagManager() {
this.$store.dispatch('ui/openTagsManagerModal');
},
openVersionsModal() {
this.$store.dispatch('ui/openVersionsModal');
},
async stopExecution () {
const executionId = this.$store.getters.activeExecutionId;
if (executionId === null) {
@ -592,6 +599,7 @@ a.logo {
.el-menu-item.updates {
color: $--sidebar-inactive-color;
font-size: 13px;
&:hover {
color: $--sidebar-active-color;

View file

@ -1,6 +1,20 @@
<template>
<div v-if="dialogVisible">
<el-drawer
v-if="drawer"
:direction="drawerDirection"
:visible="dialogVisible"
:size="drawerWidth"
>
<template v-slot:title>
<slot name="header" />
</template>
<template>
<slot name="content"/>
</template>
</el-drawer>
<el-dialog
v-else
:visible="dialogVisible"
:before-close="closeDialog"
:title="title"
@ -23,6 +37,7 @@
<script lang="ts">
import Vue from "vue";
import Drawer from "./Drawer.vue";
const sizeMap: {[size: string]: string} = {
xl: '80%',
@ -32,7 +47,10 @@ const sizeMap: {[size: string]: string} = {
export default Vue.extend({
name: "Modal",
props: ['name', 'title', 'eventBus', 'size'],
components: {
Drawer,
},
props: ['name', 'title', 'eventBus', 'size', 'drawer', 'drawerDirection', 'drawerWidth'],
mounted() {
window.addEventListener('keydown', this.onWindowKeydown);
@ -84,6 +102,11 @@ export default Vue.extend({
</script>
<style lang="scss">
.el-drawer__header {
margin: 0;
padding: 30px 30px 0 30px;; //todo
}
.dialog-wrapper {
* {
box-sizing: border-box;

View file

@ -24,17 +24,25 @@
/>
</template>
</ModalRoot>
<ModalRoot :name="VERSIONS_MODAL_KEY">
<template v-slot="{ modalName }">
<VersionsModal
:modalName="modalName"
/>
</template>
</ModalRoot>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY, VERSIONS_MODAL_KEY } from '@/constants';
import TagsManager from "@/components/TagsManager/TagsManager.vue";
import DuplicateWorkflowDialog from "@/components/DuplicateWorkflowDialog.vue";
import WorkflowOpen from "@/components/WorkflowOpen.vue";
import ModalRoot from "./ModalRoot.vue";
import VersionsModal from "./VersionsModal.vue";
export default Vue.extend({
name: "Modals",
@ -43,11 +51,13 @@ export default Vue.extend({
DuplicateWorkflowDialog,
WorkflowOpen,
ModalRoot,
VersionsModal,
},
data: () => ({
DUPLICATE_MODAL_KEY,
TAGS_MANAGER_MODAL_KEY,
WORKLOW_OPEN_MODAL_KEY,
VERSIONS_MODAL_KEY,
}),
});
</script>

View file

@ -1,8 +1,8 @@
<template>
<div class="node-icon-wrapper" :style="iconStyleData" :class="{shrink: isSvgIcon && shrink, full: !shrink}">
<div v-if="nodeIconData !== null" class="icon">
<img :src="nodeIconData.path" style="max-width: 100%; max-height: 100%;" v-if="nodeIconData.type === 'file'"/>
<font-awesome-icon :icon="nodeIconData.path" v-else-if="nodeIconData.type === 'fa'" />
<img :src="nodeIconData.fileBuffer || nodeIconData.path" style="max-width: 100%; max-height: 100%;" v-if="nodeIconData.type === 'file'"/>
<font-awesome-icon :icon="nodeIconData.icon || nodeIconData.path" v-else />
</div>
<div v-else class="node-icon-placeholder">
{{nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
@ -54,6 +54,10 @@ export default Vue.extend({
return null;
}
if (this.nodeType.iconData) {
return this.nodeType.iconData;
}
const restUrl = this.$store.getters.getRestUrl;
if (this.nodeType.icon) {

View file

@ -0,0 +1,83 @@
<template>
<a :href="version.documentationUrl" :class="$style.card">
<div :class="$style.header">
<div :class="$style.name">
Version {{version.name}}
</div>
<div :class="$style.released">
Released {{releaseDate}}
</div>
</div>
<div>
<div v-html="version.description" :class="$style.description">
</div>
<div :class="$style.nodes" v-if="version.nodes && version.nodes.length > 0">
<NodeIcon
v-for="node in version.nodes"
:key="node.name"
:nodeType="node"
/>
</div>
</div>
</a>
</template>
<script lang="ts">
import Vue from 'vue';
import { format } from 'timeago.js';
import NodeIcon from './NodeIcon.vue';
export default Vue.extend({
components: { NodeIcon },
name: 'VersionsModal',
props: ['version'],
computed: {
releaseDate() {
return format(this.version.createdAt);
},
},
});
</script>
<style module lang="scss">
.card {
background-color: #fff;
border: 1px #DBDFE7 solid;
border-radius: 8px;
display: block;
padding: 15px;
}
.header {
display: flex;
border-bottom: 1px solid #DBDFE7;
padding-bottom: 15px;
}
.name {
flex-grow: 1;
font-weight: 600;
font-size: 16px;
line-height: 18px;
color: #666666;
}
.description {
font-size: 14px;
line-height: 19px;
color: #7D7D87;
margin-top: 15px;
}
.released {
font-size: 12px;
line-height: 18px;
color: #909399;
}
.nodes {
display: flex;
margin-top: 20px;
}
</style>

View file

@ -0,0 +1,89 @@
<template>
<Modal
:name="modalName"
:drawer="true"
drawerDirection="ltr"
drawerWidth="480px"
>
<template slot="header">
<p :class="$style.title"> Weve been busy </p>
</template>
<template slot="content">
<section :class="$style['header-content']">
<p>Youre on {{ currentVersion.name }}, which is <strong>{{currentReleaseDate}}</strong> and {{ nextVersions.length }} version{{nextVersions.length > 1 ? 's' : ''}} behind the latest and greatest n8n</p>
<a><font-awesome-icon icon="info-circle"></font-awesome-icon>How to update your n8n version</a>
</section>
<section :class="$style.versions">
<VersionCard
v-for="version in nextVersions"
:key="version.name"
:version="version"
:class="$style['versions-card']"
/>
</section>
</template>
</Modal>
</template>
<script lang="ts">
import Vue from 'vue';
import { mapGetters } from 'vuex';
import { format } from 'timeago.js';
import Modal from './Modal.vue';
import VersionCard from './VersionCard.vue';
export default Vue.extend({
name: 'VersionsModal',
components: {
Modal,
VersionCard,
},
props: ['modalName'],
computed: {
...mapGetters('versions', [
'nextVersions',
'currentVersion',
]),
currentReleaseDate() {
return format(this.currentVersion.createdAt).replace('ago', 'old');
},
},
});
</script>
<style module lang="scss">
.header-content {
padding: 0px 30px;
margin-bottom: 30px;
p {
font-size: 16px;
line-height: 22px;
color: #7D7D87;
}
div {
padding-top: 20px;
}
}
.title {
margin: 0;
font-size: 24px;
line-height: 24px;
color: #555555;
font-weight: 400;
}
.versions {
background-color: #F8F9FB;
border-top: 1px #DBDFE7 solid;
height: 100%;
padding: 30px;
}
.versions-card {
margin-bottom: 15px;
}
</style>

View file

@ -14,6 +14,7 @@ export const MAX_TAG_NAME_LENGTH = 24;
export const DUPLICATE_MODAL_KEY = 'duplicate';
export const TAGS_MANAGER_MODAL_KEY = 'tagsManager';
export const WORKLOW_OPEN_MODAL_KEY = 'workflowOpen';
export const VERSIONS_MODAL_KEY = 'versions';
// breakpoints
export const BREAKPOINT_SM = 768;
@ -48,3 +49,6 @@ export const HIDDEN_NODES = ['n8n-nodes-base.start'];
export const WEBHOOK_NODE_NAME = 'n8n-nodes-base.webhook';
export const HTTP_REQUEST_NODE_NAME = 'n8n-nodes-base.httpRequest';
export const REQUEST_NODE_FORM_URL = 'https://n8n-community.typeform.com/to/K1fBVTZ3';
// versions
export const VERSIONS_BASE_URL = `https://api-staging.n8n.io/versions/`;

View file

@ -1,4 +1,4 @@
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, VERSIONS_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
import Vue from 'vue';
import { ActionContext, Module } from 'vuex';
import {
@ -19,6 +19,9 @@ const module: Module<IUiState, IRootState> = {
[WORKLOW_OPEN_MODAL_KEY]: {
open: false,
},
[VERSIONS_MODAL_KEY]: {
open: false,
},
},
modalStack: [],
sidebarMenuCollapsed: true,
@ -58,6 +61,9 @@ const module: Module<IUiState, IRootState> = {
openDuplicateModal: async (context: ActionContext<IUiState, IRootState>) => {
context.commit('openModal', DUPLICATE_MODAL_KEY);
},
openVersionsModal: async (context: ActionContext<IUiState, IRootState>) => {
context.commit('openModal', VERSIONS_MODAL_KEY);
},
},
};

View file

@ -0,0 +1,42 @@
import { getNextVersions } from '@/api/versions';
import { versions } from 'process';
import { ActionContext, Module } from 'vuex';
import {
IRootState,
IVersion,
IVersionsState,
} from '../Interface';
const module: Module<IVersionsState, IRootState> = {
namespaced: true,
state: {
nextVersions: [],
currentVersion: undefined,
},
getters: {
hasVersionUpdates(state: IVersionsState) {
return state.nextVersions.length > 0;
},
nextVersions(state: IVersionsState) {
return state.nextVersions;
},
currentVersion(state: IVersionsState) {
return state.currentVersion;
},
},
mutations: {
setVersions(state: IVersionsState, {versions, currentVersion}: {versions: IVersion[], currentVersion: string}) {
state.nextVersions = versions.filter((version) => version.name !== currentVersion);
state.currentVersion = versions.find((version) => version.name === currentVersion);
},
},
actions: {
async fetchVersions(context: ActionContext<IVersionsState, IRootState>) {
const currentVersion = context.rootState.versionCli;
const versions = await getNextVersions(currentVersion);
context.commit('setVersions', {versions, currentVersion});
},
},
};
export default module;

View file

@ -87,3 +87,6 @@ $--node-creator-description-color: #7d7d87;
// trigger icon
$--trigger-icon-border-color: #dcdfe6;
$--trigger-icon-background-color: #fff;
// drawer
$--drawer-background-color: #fff;

View file

@ -36,6 +36,7 @@ import {
import tags from './modules/tags';
import ui from './modules/ui';
import workflows from './modules/workflows';
import versions from './modules/versions';
Vue.use(Vuex);
@ -92,6 +93,7 @@ const modules = {
tags,
ui,
workflows,
versions,
};
export const store = new Vuex.Store({

View file

@ -2234,6 +2234,10 @@ export default mixins(
this.$showError(error, 'Init Problem', 'There was a problem initializing the workflow:');
}
this.stopLoading();
setTimeout(() => {
this.$store.dispatch('versions/fetchVersions');
}, 0);
});
this.$externalHooks().run('nodeView.mount');