Merge pull request #1022 from menakaj/application-mgt

Publisher UI improvements.
feature/appm-store/pbac
sinthuja 7 years ago committed by GitHub
commit 9eb25444a7

2
.gitignore vendored

@ -26,3 +26,5 @@ target
hs_err_pid* hs_err_pid*
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/node_modules/ components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/node_modules/
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/build/ components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/build/
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/package-lock.json
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/npm-debug.log

@ -22,7 +22,29 @@ package org.wso2.carbon.device.application.mgt.auth.handler.util;
public class Constants { public class Constants {
public static final String SCOPES = "perm:application:get perm:application:create perm:application:update " + public static final String SCOPES = "perm:application:get perm:application:create perm:application:update " +
"perm:application-mgt:login perm:application:delete perm:platform:add perm:platform:remove " + "perm:application-mgt:login perm:application:delete perm:platform:add perm:platform:remove " +
"perm:roles:view perm:devices:view perm:platform:get"; "perm:roles:view perm:devices:view perm:platform:get perm:admin:devices:view perm:roles:add " +
"perm:roles:add-users perm:roles:update perm:roles:permissions perm:roles:details perm:roles:view" +
" perm:roles:create-combined-role perm:roles:delete perm:dashboard:vulnerabilities " +
"perm:dashboard:non-compliant-count perm:dashboard:non-compliant perm:dashboard:by-groups " +
"perm:dashboard:device-counts perm:dashboard:feature-non-compliant perm:dashboard:count-overview " +
"perm:dashboard:filtered-count perm:dashboard:details perm:get-activity perm:devices:delete " +
"perm:devices:applications perm:devices:effective-policy perm:devices:compliance-data " +
"perm:devices:features perm:devices:operations perm:devices:search perm:devices:details " +
"perm:devices:update perm:devices:view perm:view-configuration perm:manage-configuration " +
"perm:policies:remove perm:policies:priorities perm:policies:deactivate perm:policies:get-policy-details" +
" perm:policies:manage perm:policies:activate perm:policies:update perm:policies:changes " +
"perm:policies:get-details perm:users:add perm:users:details perm:users:count perm:users:delete " +
"perm:users:roles perm:users:user-details perm:users:credentials perm:users:search perm:users:is-exist " +
"perm:users:update perm:users:send-invitation perm:admin-users:view perm:groups:devices perm:groups:update " +
"perm:groups:add perm:groups:device perm:groups:devices-count perm:groups:remove perm:groups:groups " +
"perm:groups:groups-view perm:groups:share perm:groups:count perm:groups:roles perm:groups:devices-remove " +
"perm:groups:devices-add perm:groups:assign perm:device-types:features perm:device-types:types " +
"perm:applications:install perm:applications:uninstall perm:admin-groups:count perm:admin-groups:view" +
" perm:notifications:mark-checked perm:notifications:view perm:admin:certificates:delete " +
"perm:admin:certificates:details perm:admin:certificates:view perm:admin:certificates:add " +
"perm:admin:certificates:verify perm:admin perm:devicetype:deployment perm:device-types:events " +
"perm:device-types:events:view perm:admin:device-type perm:device:enroll perm:geo-service:analytics-view " +
"perm:geo-service:alerts-manage";
public static final String[] TAGS = {"device_management"}; public static final String[] TAGS = {"device_management"};
public static final String USER_NAME = "userName"; public static final String USER_NAME = "userName";

@ -10,18 +10,18 @@
"license": "Apache License 2.0", "license": "Apache License 2.0",
"dependencies": { "dependencies": {
"axios": "^0.16.2", "axios": "^0.16.2",
"bootstrap": "^4.0.0-alpha.6", "bootstrap": "^4.0.0-beta",
"flux": "^3.1.3", "flux": "^3.1.3",
"history": "^4.7.2", "history": "^4.7.2",
"latest-version": "^3.1.0", "latest-version": "^3.1.0",
"material-ui": "^0.19.1", "material-ui": "^0.19.3",
"prop-types": "^15.5.10", "prop-types": "^15.6.0",
"qs": "^6.5.0", "qs": "^6.5.1",
"react": "^15.6.1", "react": "^15.6.2",
"react-addons-css-transition-group": "^15.6.0", "react-addons-css-transition-group": "^15.6.2",
"react-addons-transition-group": "^15.6.0", "react-addons-transition-group": "^15.6.2",
"react-dom": "^15.6.1", "react-dom": "^15.6.2",
"react-dropzone": "^4.1.2", "react-dropzone": "^4.1.3",
"react-images-uploader": "^1.1.0", "react-images-uploader": "^1.1.0",
"react-material-ui-form-validator": "^0.5.1", "react-material-ui-form-validator": "^0.5.1",
"react-modal": "^2.3.2", "react-modal": "^2.3.2",
@ -43,8 +43,8 @@
"css-loader": "^0.28.7", "css-loader": "^0.28.7",
"less": "^2.7.2", "less": "^2.7.2",
"less-loader": "^4.0.4", "less-loader": "^4.0.4",
"mocha": "^3.4.1", "mocha": "^3.5.3",
"mock-local-storage": "^1.0.2", "mock-local-storage": "^1.0.5",
"node-sass": "^4.5.3", "node-sass": "^4.5.3",
"react-intl": "^2.4.0", "react-intl": "^2.4.0",
"sass-loader": "^6.0.6", "sass-loader": "^6.0.6",

@ -23,6 +23,7 @@
<meta name="theme-color" content="#000000"> <meta name="theme-color" content="#000000">
<link rel="stylesheet" type="text/css" href="/publisher/css/font-wso2.css"> <link rel="stylesheet" type="text/css" href="/publisher/css/font-wso2.css">
<link rel="stylesheet" type="text/css" href="/publisher/themes/default/default-theme.css"> <link rel="stylesheet" type="text/css" href="/publisher/themes/default/default-theme.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<!-- <!--
manifest.json provides metadata used when your web app is added to the manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/

@ -1,6 +1,7 @@
{ {
"Title" : "Title", "Title" : "Title",
"Description" : "Description", "Description" : "Description",
"ShortDescription" : "Short Description",
"Category" : "Category", "Category" : "Category",
"Visibility" : "Visibility", "Visibility" : "Visibility",
"Devices" : "Devices", "Devices" : "Devices",
@ -8,22 +9,26 @@
"Groups" : "Groups", "Groups" : "Groups",
"Tags" : "Tags", "Tags" : "Tags",
"Platform" : "Platform", "Platform" : "Platform",
"Platforms" : "Platfomrs", "Platforms" : "Platforms",
"Applications": "Applications",
"No.Platform" : "No Platforms", "No.Platform" : "No Platforms",
"Screenshots" : "Screenshots", "Screenshots" : "Screenshots",
"Icon" : "Icon", "Icon" : "Icon",
"Info" : "Info",
"Banner" : "Banner", "Banner" : "Banner",
"Create.Application" : "Create Application", "Create.Application" : "Create Application",
"Back" : "Back", "Back" : "Back",
"Cancel" : "Cancel", "Cancel" : "Cancel",
"Finish" : "Finish", "Finish" : "Finish",
"Continue" : "Continue", "Continue" : "Continue",
"Name" : "Name",
"Application.Name" : "Application Name", "Application.Name" : "Application Name",
"General" : "General", "General" : "General",
"App.Releases" : "Application Releases", "App.Releases" : "Application Releases",
"Package.Manager" : "Package Manager", "Package.Manager" : "Package Manager",
"Save" : "Save", "Save" : "Save",
"Create.Release" : "Create Release", "Create.Release" : "Create Release",
"Release.Channel" : "Release Channel",
"Release" : "Release", "Release" : "Release",
"New.Release.For" : "New Release for", "New.Release.For" : "New Release for",
"Upload.Package.File" : "Upload Package File", "Upload.Package.File" : "Upload Package File",
@ -37,5 +42,20 @@
"Alpha.Releases" : "Alpha Releases", "Alpha.Releases" : "Alpha Releases",
"Version" : "Version", "Version" : "Version",
"Status" : "Status", "Status" : "Status",
"App.Publisher" : "Application Publisher" "App.Publisher" : "Application Publisher",
"Search.Apps" : "Search for Applications",
"View.In.Store" : "View in Store",
"Last.Updated" : "Last updated on",
"Installs" : "Installs",
"General.Info" : "General Info",
"Select.Platform": "Select Platform",
"Add.Release" : "Add Release to Application",
"Share.With.Tenants" : "Share with Tenants",
"Disable" : "Disable",
"File.Based" : "File Based",
"Activate" : "Activate",
"Yes" : "Yes",
"No" : "No",
"No.Platform.Tags" : "No Platform Tags",
"Create.Platform" : "Create Platform"
} }

@ -1,48 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.chip {
display: inline-block;
padding: 0 25px;
height: 50px;
font-size: 16px;
line-height: 50px;
border-radius: 25px;
background-color: #f1f1f1;
}
.chip img {
float: left;
margin: 0 10px 0 -25px;
height: 50px;
width: 50px;
border-radius: 50%;
}
.close-btn {
padding-left: 10px;
color: #888;
font-weight: bold;
float: right;
font-size: 20px;
cursor: pointer;
}
.close-btn:hover {
color: #000;
}

@ -16,10 +16,131 @@
* under the License. * under the License.
*/ */
@font-face {
font-family: "Roboto-Medium";
src: url('../../fonts/Roboto-Medium.woff');
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.ttf") format("ttf");
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.woff") format("woff");
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.woff2") format("woff2");
}
@font-face {
font-family: "Roboto-Regular";
src: url("../../fonts/Roboto-Regular.woff");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.ttf") format("ttf");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.woff") format("woff");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.woff2") format("woff2");
}
/*Colors*/
.primary {
color: white;
background-color: #2196f3 !important;
}
.primary-flat {
color: #2196F3 !important;
}
.danger {
color: white;
background-color: #e91e63 !important;
}
.danger-flat {
color: #e91e63 !important;
}
.grey {
color: #b3b3b3 !important;
}
/* ==================================================================== */
/* Custom button styles based on material design specs. */
.custom-raised {
font-family: Roboto-Medium;
text-transform: uppercase !important;
font-size: 14px !important;
padding-left: 16px !important;
border-radius: 2px !important;
padding-right: 16px !important;
height: 36px !important;
border: none !important;
}
.custom-raised:hover {
cursor: pointer;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
background-color: #1976D2 !important;
}
.custom-raised:focus {
box-shadow: none !important;
-webkit-box-shadow: none !important;
background-color: #1976D2 !important;
}
.custom-flat {
font-family: Roboto-Medium;
height: 36px !important;
border-radius: 2px !important;
margin-left: 8px !important;
margin-right: 8px !important;
padding-left: 8px !important;
padding-right: 8px !important;
background-color: transparent !important;
text-transform: uppercase;
outline: none !important;
border: none !important;
}
.custom-flat:hover {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.12) !important;
}
.custom-flat:focus {
outline: none !important;
border: none !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
background-color: rgba(0, 0, 0, 0.40) !important;
}
.circle-button {
border-radius: 100% !important;
height: 36px !important;
width: 36px;
}
/* ==================================================================== */
/* Body Styling */ /* Body Styling */
body { body {
width: 100%; width: 100%;
font-family: Roboto sans-serif; font-family: "Roboto-Regular" !important;
background-color: #e8e8e8 !important;
}
.app-manager-title {
font-family: "Roboto-Medium";
font-size: 20px;
}
.app-manager-sub-title {
font-family: "Roboto-Regular";
font-size: 18px;
}
#app-mgt-footer {
clear: both;
position: relative;
height: 50px;
width: 100%;
color: white;
background-color: #334d88;
} }
/* Login page styles*/ /* Login page styles*/
@ -31,35 +152,55 @@ body {
border-radius: 0; border-radius: 0;
} }
#login-btn { .login-btn {
border-radius: 0; float: right;
background-color: navy;
color: white;
cursor: pointer;
} }
#login-container { .login-header {
width: 50%; background-color: #3f50b5;
margin: 0 auto color: white;
height: 128px;
width: 100%;
margin: 0 !important;
padding: 20px;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
} }
#login-card { #login-card {
width: 25%;
height: 50%;
margin: 10% auto;
font-family: Roboto-Regular;
font-size: 14px;
border-radius: 0; border-radius: 0;
background-color: #BaBaBa; background-color: #ffffff;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
} }
/* Base layout container */ .login-header-title {
#container { font-family: Roboto-Medium;
background-color: #ffffff; font-size: 20px;
padding: 0; font-weight: 500;
}
.login-header-logo {
height: 70px;
width: 150px;
} }
.login-form {
margin: 0 !important;
padding: 40px;
}
/* Base layout container */
/* Base layout header content*/ /* Base layout header content*/
#header-content { .header-content {
height: 125px; height: 128px !important;
width: 100%; width: 100% !important;
margin: 0 10px 0 0; margin: 0 10px 0 0;
background-color: #3b33bd; background-color: #3f50b5 !important;
position: fixed; /* Set the navbar to fixed position */ position: fixed; /* Set the navbar to fixed position */
top: 0; /* Position the navbar at the top of the page */ top: 0; /* Position the navbar at the top of the page */
z-index: 2; z-index: 2;
@ -67,22 +208,59 @@ body {
} }
/* Contains the header styles.*/ /* Contains the header styles.*/
#header { .header {
margin: 16px 16px 20px 16px; padding: 24px 24px 10px 24px;
height: 100%; /*margin: 16px 16px 20px 16px;*/
position: relative; position: relative;
} }
#header-text { #header-text {
color: #ffffff; color: #ffffff;
font-size: 25px; font-size: 20px;
font-family: Roboto-Medium;
top: 10px; top: 10px;
margin-left: 10px; margin-left: 10px;
} }
/* The buttons in the header (User and Notification)*/ /* The buttons in the header (User and Notification)*/
#header-btn-container { .header-button-container {
float: right; display: flex;
justify-content: flex-end;
}
.header-user-name {
font-family: Roboto-Medium;
font-size: 14px;
padding-top: 15px;
color: white;
}
.header-image {
height: 43px;
width: 100px;
margin-right: 24px;
}
#header-button {
border-radius: 50%;
background-color: transparent;
border: none;
height: 50px;
width: 50px;
margin-right: 10px;
position: relative;
outline: none;
}
#header-button:hover {
background-color: #4353bd;
cursor: pointer;
}
#header-button i {
position: absolute;
bottom: 19px;
left: 17px;
} }
.btn-header { .btn-header {
@ -91,21 +269,25 @@ body {
color: white; color: white;
} }
/* Search box styles */ #sub-title {
.search-icon { font-family: Roboto-Regular;
position: absolute; font-size: 18px;
top: 5px; font-weight: 600;
left: 5px; padding-top: 5px;
padding-left: 18px;
color: RGBA(0, 0, 0, 1);
} }
#search-box { /* Search box styles */
.search-box {
display: flex; display: flex;
color: #a8a8a8;
position: relative;
float: right; float: right;
top: 75px; }
left: 80px;
margin-right: 16px; .search-box i {
position: absolute;
top: 5px;
color: #BaBaBa;
} }
#search { #search {
@ -123,38 +305,114 @@ body {
/* Application Add button */ /* Application Add button */
#add-btn-container { #add-btn-container {
position: absolute; position: absolute;
left: 12%; top: 98px;
top: 100px; }
.add-btn {
background-color: #ff5722;
}
.add-btn:hover {
background-color: #E64A19;
}
#app-main-content {
margin: 0 auto;
}
#sub-title-container {
height: 100px;
padding: 50px 0 20px 0;
}
.application-container {
padding: 0 !important;
min-height: 100% !important;
margin-top: 128px !important;
} }
/* Holds the app publisher pages. */ /* Holds the app publisher pages. */
#application-content { #application-content {
height: auto; height: auto;
width: 80%; background-color: white;
margin: 150px auto; width: 100%;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); box-shadow: 2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
padding: 10px 10px 10px 10px; padding: 24px;
} }
.stepper-header { .stepper-header {
width: 100%; width: 100%;
border-bottom: solid 1px cornflowerblue; color: #BaBaBa;
padding-bottom: 5px; padding-bottom: 5px;
margin-bottom: 10px; margin-bottom: 10px;
} }
#application-tag {
margin: 0 2px 0 2px;
background-color: blue;
height: 20px;
}
#application-tag:hover {
cursor: pointer;
background-color: #007bff;
}
.platform-link-placeholder { .platform-link-placeholder {
color: #888888; color: #888888;
float: right; float: right;
margin-right: 20px;
padding-bottom: 10px; padding-bottom: 10px;
} }
.platform-link-placeholder i {
margin-right: 4px;
}
#application-list { #application-list {
margin-top: 20px;
transition: margin-right .5s; transition: margin-right .5s;
} }
#batch-content {
display: flex;
margin-top: 5px;
}
.app-list-icon {
border-radius: 50%;
height: 50px;
width: 50px
}
.app-table-row {
height: 62px;
cursor: pointer;
padding-top: 6px;
font-family: "Roboto-Regular";
font-size: medium;
}
.app-table-row:hover {
color: white;
background-color: #3f50b5;
}
.app-list-table-header {
margin-top: 30px;
margin-bottom: 10px;
font-family: "Roboto-Medium";
font-size: 15px;
}
.app-view-image {
height: 100px;
width: 100px;
border-radius: 50%;
}
#app-visibility-default {
display: none;
}
#app-image-screenshot { #app-image-screenshot {
width: 300px; width: 300px;
height: 300px; height: 300px;
@ -170,12 +428,15 @@ body {
height: 300px; height: 300px;
} }
#form-error {
color: red;
}
.application-create-banner-dropzone { .application-create-banner-dropzone {
width: 300px; width: 300px;
height: 150px; height: 150px;
border-radius: 5%; border-radius: 5%;
position: relative; position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px; border: dashed #888888 2px;
} }
@ -191,7 +452,6 @@ body {
margin: 0 5px 0 5px; margin: 0 5px 0 5px;
border-radius: 10%; border-radius: 10%;
position: relative; position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px; border: dashed #888888 2px;
} }
@ -206,7 +466,6 @@ body {
height: 150px; height: 150px;
border-radius: 10%; border-radius: 10%;
position: relative; position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px; border: dashed #888888 2px;
} }
@ -217,6 +476,7 @@ body {
} }
#screenshot-container { #screenshot-container {
max-width: 600px;
display: flex; display: flex;
overflow-x: auto; overflow-x: auto;
height: 200px; height: 200px;
@ -232,6 +492,13 @@ body {
overflow-y: auto; overflow-y: auto;
} }
.step-index {
height: 20px;
width: 20px;
background-color: #2196F3;
border-radius: 50%;
}
#img-btn-screenshot { #img-btn-screenshot {
margin: 0 5px 0 5px; margin: 0 5px 0 5px;
} }
@ -239,6 +506,7 @@ body {
#app-create-modal { #app-create-modal {
max-width: 700px; max-width: 700px;
overflow-x: auto; overflow-x: auto;
border-radius: 0% !important;
} }
#store { #store {
@ -276,12 +544,13 @@ body {
} }
/* Application View */ /* Application View */
#application-view-content { #application-view-content {
width: 100%; width: 100%;
} }
#application-view-row { #application-view-row {
margin: 10px 10px 20px 20px; margin: 10px 10px 0 20px;
} }
#app-icon { #app-icon {
@ -293,7 +562,6 @@ body {
.app-updated-date { .app-updated-date {
color: #888888; color: #888888;
font-style: italic;
} }
.app-install-count { .app-install-count {
@ -315,12 +583,13 @@ body {
} }
/* Application Edit Base Layout */ /* Application Edit Base Layout */
#application-edit-header { #application-edit-header {
height: 50px; height: 40px;
width: 100%; width: 100%;
margin: 0; margin-top: 20px;
font-size: 20px; margin-bottom: 20px;
border-bottom: solid 1px #d8d8d8; font-size: 25px;
} }
.application-header-text { .application-header-text {
@ -348,6 +617,7 @@ body {
} }
/*Tab styling*/ /*Tab styling*/
div.tab { div.tab {
float: left; float: left;
border-right: 1px solid #d8d8d8; border-right: 1px solid #d8d8d8;
@ -355,6 +625,7 @@ div.tab {
} }
/* Style the tab buttons */ /* Style the tab buttons */
div.tab button { div.tab button {
display: block; display: block;
background-color: inherit; background-color: inherit;
@ -369,30 +640,25 @@ div.tab button {
} }
/* Change background color of buttons on hover */ /* Change background color of buttons on hover */
div.tab button:hover { div.tab button:hover {
background-color: #ddd6d7; background-color: #ddd6d7;
cursor: pointer; cursor: pointer;
} }
/* Create an active/current "tab button" class */ /* Create an active/current "tab button" class */
div.tab button.active { div.tab button.active {
background-color: #1b3bcc; background-color: #1b3bcc;
color: white; color: white;
} }
#application-edit-main-container { #application-edit-main-container {
display: flex; display: flex;
} }
#application-edit-outer-content { #application-edit-outer-content {
height: auto; height: auto;
width: 100%;
}
#application-edit-content {
margin: 5px 10px 5px 10px;
width: 90%;
} }
#app-edit-content { #app-edit-content {
@ -402,13 +668,13 @@ div.tab button.active {
.back-to-app { .back-to-app {
position: absolute; position: absolute;
height: 40px; height: 50px;
width: 40px; width: 50px;
border-radius: 50%; border-radius: 50%;
} }
.back-to-app i { .back-to-app i {
padding: 10px 10px 10px 10px; padding: 12px 10px 10px 12px;
} }
.back-to-app:hover { .back-to-app:hover {
@ -418,6 +684,7 @@ div.tab button.active {
} }
/* Create Release and Release management */ /* Create Release and Release management */
.release-header { .release-header {
margin-top: 20px; margin-top: 20px;
margin-bottom: 20px; margin-bottom: 20px;
@ -472,6 +739,7 @@ div.tab button.active {
} }
/* Application Edit General Info */ /* Application Edit General Info */
.app-edit-general-info { .app-edit-general-info {
margin-top: 20px; margin-top: 20px;
max-width: 100%; max-width: 100%;
@ -481,3 +749,92 @@ div.tab button.active {
float: right; float: right;
margin-bottom: 10px; margin-bottom: 10px;
} }
.app-view-field {
font-family: Roboto-Medium;
font-size: 14px;
}
.app-view-text {
font-family: Roboto-Regular;
font-size: 14px;
}
/* Platform Specific Styles. */
#platform-listing {
margin: 10px;
}
.create-platform i {
margin-right: 10px;
}
#platform-list {
margin-top: 20px;
display: flex;
flex-flow: wrap;
}
.platform-content {
margin: 10px;
padding-top: 16px;
box-shadow: 2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.platform-content .row {
margin: 0;
}
.platform-content .col {
padding: 0;
}
.platform-content-basic {
padding: 0 16px 0 16px;
display: flex;
}
.platform-content-more-outer {
}
.platform-content-more {
padding: 16px 16px 24px 16px;
}
.platform-content-footer {
display: flex;
padding: 8px 8px 8px 8px;
}
.platform-text-container {
padding: 8px 16px 0 16px;
}
.circle-button {
float: right;
}
.platform-icon-letter {
text-align: center;
text-transform: uppercase;
font-family: Roboto-Medium;
font-size: 70px;
color: white;
padding-top: 15px;
}
.platform-icon-container {
height: 120px;
width: 120px;
background-color: #01579B;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
}
.platform-content:hover {
}
.data-table-row-cell {
padding-top: 14px;
}

@ -35,8 +35,8 @@ export default class ApplicationMgtApi {
* *
* From applicationData, the proper application object will be created and send it to the api. * From applicationData, the proper application object will be created and send it to the api.
* */ * */
static createApplication(applicationData) { static createApplication(generalInfo, platform, screenshots, release) {
let {application, images} = Helper.buildApplication(applicationData); let {application, images} = Helper.buildApplication(generalInfo, platform, screenshots, release);
const headers = AuthHandler.createAuthenticationHeaders("application/json"); const headers = AuthHandler.createAuthenticationHeaders("application/json");
console.log(application); console.log(application);
console.log(images); console.log(images);

@ -25,26 +25,22 @@ export default class Helper {
/** /**
* Generate application object from form data passed. * Generate application object from form data passed.
* @param appData: Application data from the application creation form. * @param generalInfo: Application data from the application creation form.
* @param platform
* @param screenshots
* @param release
* @return {Object, Object}: The application object and the set of images related to the application. * @return {Object, Object}: The application object and the set of images related to the application.
* */ * */
static buildApplication(appData) { static buildApplication(generalInfo, platform, screenshots, release) {
let application = {}; let images = screenshots;
let images = {}; let application = Object.assign({}, generalInfo, platform);
for (let prop in application) {
for (let step in appData) { if (prop === 'tags') {
let tmpData = appData[step].data.step; application[prop] = Helper.stringifyTags(generalInfo[prop]);
for (let prop in tmpData) {
if (prop === 'banner' || prop === 'screenshots' || prop === 'icon') {
images[prop] = tmpData[prop];
} else if(prop === 'tags') {
application[prop] = Helper.stringifyTags(tmpData[prop]);
} else {
application[prop] = tmpData[prop];
}
} }
} }
console.log(application);
return {application, images}; return {application, images};
} }

@ -15,24 +15,24 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React, {Component} from 'react';
'use strict';
/** /**
* Platform view component. * This Utility class will contain basic methods for form validation.
* */ * */
class PlatformView extends Component { export const validateURL = (value) => {
};
constructor() { export const validateNull = (value) => {
super(); return !value;
} };
render() { export const validateEmpty = (array) => {
return ( return array.length > 0;
<div> };
Platform View
</div>
);
}
}
export default PlatformView; export const validateEmptyObject = (object) => {
return Object.keys(object).length > 0;
};

@ -20,10 +20,11 @@ import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import {withRouter} from 'react-router-dom'; import {withRouter} from 'react-router-dom';
import AuthHandler from "../../api/authHandler"; import AuthHandler from "../../api/authHandler";
import {Button, Col, Container, Input, Row,} from 'reactstrap';
import ApplicationCreate from '../Application/Create/ApplicationCreate'; import ApplicationCreate from '../Application/Create/ApplicationCreate';
import {Col, Container, Input, Row,} from 'reactstrap';
import FloatingButton from "../UIComponents/FloatingButton/FloatingButton"; import FloatingButton from "../UIComponents/FloatingButton/FloatingButton";
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import Logo from "../UIComponents/Logo/Logo";
/** /**
* Base Layout: * Base Layout:
@ -35,13 +36,21 @@ class BaseLayout extends Component {
constructor() { constructor() {
super(); super();
this.logout = this.logout.bind(this);
this.closeModal = this.closeModal.bind(this);
this.onClickPlatforms = this.onClickPlatforms.bind(this);
this.onClickApplications = this.onClickApplications.bind(this);
this.state = { this.state = {
notifications: 0, notifications: 0,
user: 'Admin', user: '',
openModal: false openModal: false,
currentPage: "",
logo: {}
}; };
this.logout = this.logout.bind(this); }
this.closeModal = this.closeModal.bind(this);
componentWillMount() {
this.setState({user: this.props.user});
} }
handleApplicationClick() { handleApplicationClick() {
@ -70,45 +79,112 @@ class BaseLayout extends Component {
this.setState({openModal: false}); this.setState({openModal: false});
} }
onClickPlatforms() {
window.location.href = "/publisher/assets/platforms";
this.setState({currentPage: "Platforms"})
}
onClickApplications() {
window.location.href = "/publisher/assets/apps";
}
getCurrentPageTitle() {
let href = window.location.href;
if (href.indexOf("apps") !== -1) {
return "Applications";
} else if (href.indexOf("platforms") !== -1) {
return "Platforms";
}
}
render() { render() {
const userName = this.state.user._userName[0];
return ( return (
<Container noGutters fluid id="container"> <div>
<div id="header-content"> <div className="header-content">
<div id="header"> <div className="header">
<span id="header-text"> <Row>
WSO2 IoT <FormattedMessage id="App.Publisher" defaultMessage="Application Publisher"/> <Col md="6">
</span> <span id="header-text">
<div id="header-btn-container"> <Logo className="header-image" image_name="logo.png"/>
<i className="fw fw-notification btn-header"></i> IoT <FormattedMessage id="App.Publisher" defaultMessage="Application Publisher"/>
<i className="fw fw-user btn-header"></i> </span>
</div> </Col>
<div id="search-box"> <Col>
<i className="fw fw-search search-icon"> <div className="header-button-container">
</i> <Button id="header-button">
<Input <i className="fw fw-notification btn-header"></i></Button>
id="search" <span className="header-user-name">
name="search" {userName.charAt(0).toUpperCase() + userName.slice(1)}
placeholder={'Search for Applications'} </span>
onChange={(event) => console.log(event.target.value)} //TODO: Remove this <Button id="header-button">
<i className="fw fw-user btn-header"></i></Button>
</div>
</Col>
</Row>
<Row>
<Col>
<div className="search-box">
<i className="fw fw-search"></i>
<Input
id="search"
name="search"
placeholder={'Search for Applications'}
onChange={(event) => console.log(event.target.value)} //TODO: Remove this
/>
</div>
</Col>
</Row>
</div>
<Container>
<div id="add-btn-container">
<FloatingButton
className="add-btn small"
onClick={this.handleApplicationCreateClick.bind(this)}
/> />
</div> </div>
</div> </Container>
<div id="add-btn-container">
<FloatingButton
className="add-btn small"
onClick={this.handleApplicationCreateClick.bind(this)}
/>
</div>
</div>
<div id="application-content" style={this.state.style}>
<Row>
<Col>
{this.props.children}
</Col>
</Row>
</div> </div>
<Container className="application-container">
<div id="app-main-content">
<Row id="sub-title-container">
<Col>
<div id="sub-title">
{/*TODO: Add the current page title*/}
{this.getCurrentPageTitle()}
</div>
</Col>
<Col>
<div className="platform-link-placeholder">
{this.getCurrentPageTitle() === "Applications" ?
<Button className="custom-flat grey" onClick={this.onClickPlatforms}>
<i className="fw fw-settings"></i>
<FormattedMessage id="Platforms" defaultMessage="Platforms"/>
</Button> :
<Button className="custom-flat grey" onClick={this.onClickApplications}>
<i className="fw fw-application"></i>
<FormattedMessage id="Applications" defaultMessage="Applications"/>
</Button>
}
</div>
</Col>
</Row>
<Row>
<div id="application-content">
<Row>
<Col>
{this.props.children}
</Col>
</Row>
</div>
</Row>
</div>
</Container>
<ApplicationCreate open={this.state.openModal} close={this.closeModal}/> <ApplicationCreate open={this.state.openModal} close={this.closeModal}/>
</Container> </div>
); );
} }
} }

@ -18,10 +18,11 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {withRouter} from 'react-router-dom'; import {withRouter} from 'react-router-dom';
import {Button, Col, Row, Table} from 'reactstrap'; import {Button, Col, Row} from 'reactstrap';
import Drawer from '../UIComponents/Drawer/Drawer'; import Drawer from '../UIComponents/Drawer/Drawer';
import ApplicationView from './View/ApplicationView'; import ApplicationView from './View/ApplicationView';
import {FormattedMessage} from 'react-intl'; import ApplicationMgtApi from "../../api/applicationMgtApi";
import AuthHandler from "../../api/authHandler";
/** /**
* The App Create Component. * The App Create Component.
@ -32,14 +33,15 @@ import {FormattedMessage} from 'react-intl';
* When the wizard is completed, data will be arranged and sent to the api. * When the wizard is completed, data will be arranged and sent to the api.
* */ * */
class ApplicationListing extends Component { class ApplicationListing extends Component {
constructor() { constructor() {
super(); super();
this.searchApplications = this.searchApplications.bind(this); this.searchApplications = this.searchApplications.bind(this);
this.onRowClick = this.onRowClick.bind(this); this.onRowClick = this.onRowClick.bind(this);
this.setData = this.setData.bind(this);
this.sortData = this.sortData.bind(this); this.sortData = this.sortData.bind(this);
this.compare = this.compare.bind(this); this.compare = this.compare.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this); this.onAppEditClick = this.onAppEditClick.bind(this);
this.getSelectedApplication = this.getSelectedApplication.bind(this);
this.state = { this.state = {
searchedApplications: [], searchedApplications: [],
applications: [], applications: [],
@ -57,7 +59,6 @@ class ApplicationListing extends Component {
}; };
} }
applications = [ applications = [
{ {
id: "3242342ffww3423", id: "3242342ffww3423",
@ -84,35 +85,59 @@ class ApplicationListing extends Component {
}, },
]; ];
componentWillMount() { headers = [
{
// let getApps = ApplicationMgtApi.getApplications(); data_id: "image",
// getApps.then(response => { data_type: "image",
// let apps = this.setData(response.data.applications); sortable: false,
// console.log(apps); //TODO: Remove this. label: ""
// this.setState({searchedApplications: apps}); },
// // console.log(this.setState({data: response.data}), console.log(this.state)); {
// }).catch(err => { data_id: "applicationName",
// AuthHandler.unauthorizedErrorHandler(err); data_type: "string",
// }); sortable: true,
} locale: "Application.name",
label: "Application Name",
/** sort: this.sortData
* Extract application from application list and update the state. },
* */ {
setData(applications) { data_id: "platform",
let apps = []; data_type: "image_array",
for (let app in applications) { sortable: false,
let application = {}; locale: "Platform",
application.id = applications[app].uuid; label: "Platform"
application.applicationName = applications[app].name; },
application.platform = applications[app].platform.name; {
application.category = applications[app].category.id; data_id: "category",
application.status = applications[app].currentLifecycle.lifecycleState.name; data_type: "string",
apps.push(application); sortable: false,
locale: "Category",
label: "Category"
},
{
data_id: "status",
data_type: "string",
sortable: false,
locale: "Status",
label: "Status"
},
{
data_id: "edit",
data_type: "button",
sortable: false,
label: ""
} }
];
this.setState({searchedApplications: apps}); componentWillMount() {
let getApps = ApplicationMgtApi.getApplications();
getApps.then(response => {
console.log(response);
this.setState({searchedApplications: response.data.applications});
}).catch(err => {
AuthHandler.unauthorizedErrorHandler(err);
});
} }
/** /**
@ -154,34 +179,22 @@ class ApplicationListing extends Component {
return 0; return 0;
} }
onRowClick() { onRowClick(uuid) {
let selectedApp = this.getSelectedApplication(uuid);
let style = { let style = {
width: '500px', width: '550px',
marginLeft: '500px' marginLeft: '550px'
}; };
let appListStyle = { let appListStyle = {
marginRight: '500px', marginRight: '550px',
}; };
this.setState({drawer: style, appListStyle: appListStyle}); this.setState({drawer: style, appListStyle: appListStyle, application: selectedApp[0]});
} }
handleButtonClick() { onAppEditClick(uuid) {
console.log("Application Listing"); this.props.history.push("apps/edit/" + uuid);
this.props.history.push("apps/edit/fdsfdsf343");
}
remove(imageId) {
let tmp = this.state.image;
console.log(imageId);
let rem = tmp.filter((image) => {
return image.id !== imageId
});
this.setState({image: rem});
} }
closeDrawer() { closeDrawer() {
@ -196,70 +209,56 @@ class ApplicationListing extends Component {
this.setState({drawer: style, appListStyle: appListStyle}); this.setState({drawer: style, appListStyle: appListStyle});
} }
getSelectedApplication(uuid) {
return this.state.searchedApplications.filter(application => {
return application.uuid === uuid;
});
}
render() { render() {
//TODO: Move this to a data table component.
return ( return (
<div id="application-list" style={this.state.appListStyle}> <div id="application-list" style={this.state.appListStyle}>
<Row> <Row className="app-list-table-header">
<Col xs="3 offset-9"> {this.headers.map(header => {
<div className="platform-link-placeholder"> if (header.data_id === "applicationName") {
<Button><i className="fw fw-settings"></i> return (
<FormattedMessage id="Platforms" defaultMessage="Platforms"/> <Col xs="5">{header.label}</Col>)
</Button> } else if (header.data_id === "image") {
</div> return (<Col xs="1">{header.label}</Col>)
</Col> }
</Row> return (<Col>{header.label}</Col>)
<Row> })}
<Col>
<Table striped hover>
<thead>
<tr>
<th></th>
{/* TODO: Remove console.log and add sort method. */}
<th onClick={() => {
console.log("sort")
}}>
<FormattedMessage id="Application.Name" defaultMessage="Application Name"/>
</th>
<th><FormattedMessage id="Category" defaultMessage="Category"/></th>
<th><FormattedMessage id="Platform" defaultMessage="Platform"/></th>
<th><FormattedMessage id="Status" defaultMessage="Status"/></th>
<th></th>
</tr>
</thead>
<tbody>
{this.applications.map(
(application) => {
return (
<tr key={application.id} onClick={this.onRowClick}>
<td>
{/* TODO: Move this styles to css. */}
<img
src={application.icon}
height='50px'
width='50px'
style={{border: 'solid 1px black', borderRadius: "100%"}}
/>
</td>
<td>{application.applicationName}</td>
<td>{application.category}</td>
<td>{application.platform}</td>
<td>{application.status}</td>
<td>
<Button onClick={this.handleButtonClick}>
<i className="fw fw-edit"></i>
</Button>
</td>
</tr>
)
}
)}
</tbody>
</Table>
</Col>
</Row> </Row>
<hr/>
{this.state.searchedApplications.map(application => {
return (
<Row className="app-table-row" onClick={() => {
this.onRowClick(application.uuid)
}}>
<Col xs="1">
<img
className="app-list-icon"
src={application.icon}
/>
</Col>
<Col xs="5" className="data-table-row-cell"><strong>{application.name}</strong></Col>
<Col className="data-table-row-cell">{application.platform.name}</Col>
<Col className="data-table-row-cell">{application.category.name}</Col>
<Col
className="data-table-row-cell">{application.currentLifecycle.lifecycleState.name}
</Col>
<Col>
<Button className="custom-flat grey rounded"
onClick={() => this.onAppEditClick(application.uuid)}>
<i className="fw fw-edit"></i>
</Button>
</Col>
</Row>
)
})}
<Drawer onClose={this.closeDrawer.bind(this)} style={this.state.drawer}> <Drawer onClose={this.closeDrawer.bind(this)} style={this.state.drawer}>
<ApplicationView/> <ApplicationView application={this.state.application}/>
</Drawer> </Drawer>
</div> </div>
); );

@ -21,9 +21,10 @@ import {withRouter} from 'react-router-dom';
import AuthHandler from "../../../api/authHandler"; import AuthHandler from "../../../api/authHandler";
import {Step1, Step2, Step3, Step4} from './CreateSteps/index'; import {Step1, Step2, Step3, Step4} from './CreateSteps/index';
import ApplicationMgtApi from '../../../api/applicationMgtApi'; import ApplicationMgtApi from '../../../api/applicationMgtApi';
import {Button, Col, Modal, ModalBody, ModalFooter, ModalHeader, Row} from 'reactstrap'; import {Col, Modal, ModalBody, ModalHeader, Row} from 'reactstrap';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
/** /**
* The App Create Component. * The App Create Component.
* *
@ -49,7 +50,11 @@ class ApplicationCreate extends Component {
finished: false, finished: false,
stepIndex: 0, stepIndex: 0,
stepData: [], stepData: [],
isDialogOpen: false isDialogOpen: false,
generalInfo: {},
platform: {},
screenshots: {},
release: {}
}; };
} }
@ -61,17 +66,18 @@ class ApplicationCreate extends Component {
this.setState({open: this.props.open}); this.setState({open: this.props.open});
} }
/**
* Resets the form and closes the modal.
* */
onClose() { onClose() {
this.setState({stepIndex: 0}, this.props.close()); this.setState({stepIndex: 0, generalInfo: {}, platform: {}, screenshots: {}, release: {}}, this.props.close());
} }
/** /**
* Handles next button click event. * Handles next button click event.
* */ * */
onNextClick() { onNextClick() {
console.log("Handle Next"); //TODO: Remove this console.log(this.state.stepIndex); //TODO: Remove this
const {stepIndex} = this.state; const {stepIndex} = this.state;
this.setState({ this.setState({
stepIndex: stepIndex + 1, stepIndex: stepIndex + 1,
@ -83,8 +89,8 @@ class ApplicationCreate extends Component {
* Handles form submit. * Handles form submit.
* */ * */
onSubmit() { onSubmit() {
let stepData = this.state.stepData; let {generalInfo, platform, screenshots, release} = this.state;
let applicationCreationPromise = ApplicationMgtApi.createApplication(stepData); let applicationCreationPromise = ApplicationMgtApi.createApplication(generalInfo, platform, screenshots, release);
applicationCreationPromise.then(response => { applicationCreationPromise.then(response => {
this.handleYes(); this.handleYes();
} }
@ -108,10 +114,10 @@ class ApplicationCreate extends Component {
* This clears the data in the current step and returns to the previous step. * This clears the data in the current step and returns to the previous step.
* */ * */
onPrevClick() { onPrevClick() {
console.log(this.state.stepIndex);
const {stepIndex} = this.state; const {stepIndex} = this.state;
if (stepIndex > 0) { if (stepIndex > 0) {
this.removeStepData(); this.setState({stepIndex: stepIndex - 1, finished: false});
this.setState({stepIndex: stepIndex - 1});
} }
}; };
@ -121,11 +127,25 @@ class ApplicationCreate extends Component {
* @param data: The form data of the step. * @param data: The form data of the step.
* */ * */
setStepData(step, data) { setStepData(step, data) {
console.log(step, data, this.state.stepData); //TODO: Remove this console.log(step, data, this.state); //TODO: Remove this
let tmpStepData = this.state.stepData; switch (step) {
tmpStepData.push({step: step, data: data}); case "generalInfo": {
this.setState({generalInfo: data}, this.onNextClick());
this.setState({stepData: tmpStepData}, this.onNextClick()) break;
}
case "platform": {
this.setState({platform: data}, this.onNextClick());
break;
}
case "screenshots": {
this.setState({screenshots: data}, this.onNextClick());
break;
}
case "release": {
this.setState({release: data}, this.onNextClick());
break;
}
}
}; };
/** /**
@ -134,9 +154,10 @@ class ApplicationCreate extends Component {
removeStepData() { removeStepData() {
let tempData = this.state.stepData; let tempData = this.state.stepData;
tempData.pop(); tempData.pop();
this.setState({stepData: tempData}); this.setState({stepData: tempData, stepIndex: 0});
}; };
/* ----------------- Deprecated ----------------- */
/** /**
* Handles the Yes button in app creation cancellation dialog. * Handles the Yes button in app creation cancellation dialog.
* Clears all the form data and reset the wizard. * Clears all the form data and reset the wizard.
@ -153,6 +174,8 @@ class ApplicationCreate extends Component {
this.setState({isDialogOpen: false}); this.setState({isDialogOpen: false});
}; };
/* ---------------------------------------------- */
/** /**
* Defines all the Steps in the stepper. (Wizard) * Defines all the Steps in the stepper. (Wizard)
* *
@ -167,35 +190,36 @@ class ApplicationCreate extends Component {
case 0: case 0:
return ( return (
<Step1 <Step1
handleNext={this.onNextClick} defaultData={this.state.generalInfo}
setData={this.setStepData} setStepData={this.setStepData}
removeData={this.removeStepData} close={this.onClose}
/> />
); );
case 1: case 1:
return ( return (
<Step2 <Step2
handleNext={this.onNextClick} defaultData={this.state.platform}
handlePrev={this.onPrevClick} handlePrev={this.onPrevClick}
setData={this.setStepData} setStepData={this.setStepData}
removeData={this.removeStepData} close={this.onClose}
/> />
); );
case 2: case 2:
return ( return (
<Step3 <Step3
handleFinish={this.onNextClick} defaultData={this.state.screenshots}
handlePrev={this.onPrevClick} handlePrev={this.onPrevClick}
setData={this.setStepData} setStepData={this.setStepData}
removeData={this.removeStepData} close={this.onClose}
/> />
); );
case 3: { case 3: {
return ( return (
<Step4 <Step4
handleNext={this.onNextClick} defaultData={this.state.release}
setData={this.setStepData} handlePrev={this.onPrevClick}
removeData={this.removeStepData} onSubmit={this.onSubmit}
close={this.onClose}
/> />
) )
} }
@ -204,6 +228,10 @@ class ApplicationCreate extends Component {
} }
} }
setStepHeader(stepIndex) {
}
render() { render() {
const {finished, stepIndex} = this.state; const {finished, stepIndex} = this.state;
@ -218,7 +246,35 @@ class ApplicationCreate extends Component {
<Row> <Row>
<Col> <Col>
<div className="stepper-header"> <div className="stepper-header">
<Row>
<Col>
<div className="stepper-header-content">
<div className="step-index">1</div>
<div className="step-header">
<FormattedMessage id="General.Info" defaultMessage="General.Info"/>
</div>
</div>
</Col>
<Col>
<span className="step-index">2</span>
<span className="step-header">
<FormattedMessage id="Select.Platform"
defaultMessage="Select.Platform"/>
</span>
</Col>
<Col>
<span className="step-index">3</span>
<span className="step-header">
<FormattedMessage id="Screenshots" defaultMessage="Screenshots"/>
</span>
</Col>
<Col>
<span className="step-index">4</span>
<span className="step-header">
<FormattedMessage id="Release" defaultMessage="Release"/>
</span>
</Col>
</Row>
</div> </div>
</Col> </Col>
</Row> </Row>
@ -228,22 +284,6 @@ class ApplicationCreate extends Component {
</Col> </Col>
</Row> </Row>
</ModalBody> </ModalBody>
<ModalFooter>
{stepIndex === 0 ? <div/> :
<Button color="primary" onClick={this.onPrevClick}>
<FormattedMessage id="Back" defaultMessage="Back"/>
</Button>}
<Button color="secondary" onClick={this.onClose}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
{finished ?
<Button color="primary" onClick={this.onSubmit}>
<FormattedMessage id="Finish" defaultMessage="Finish" />
</Button> :
<Button color="primary" onClick={this.onNextClick}>
<FormattedMessage id="Continue" defaultMessage="Continue"/>
</Button>}
</ModalFooter>
</Modal> </Modal>
</div>); </div>);
} }

@ -18,8 +18,10 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Badge, FormGroup, Input, Label} from 'reactstrap';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {Button, Form, FormFeedback, FormGroup, Input, Label, ModalFooter} from 'reactstrap';
import * as validator from '../../../../common/validator';
import Chip from "../../../UIComponents/Chip/Chip";
/** /**
* The Second step of application create wizard. * The Second step of application create wizard.
@ -42,22 +44,34 @@ import {FormattedMessage} from 'react-intl';
class Step1 extends Component { class Step1 extends Component {
constructor() { constructor() {
super(); super();
this.onTextFieldChange = this.onTextFieldChange.bind(this);
this.setStepData = this.setStepData.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.onVisibilityChange = this.onVisibilityChange.bind(this);
this.onVisibilityItemSelect = this.onVisibilityItemSelect.bind(this);
this.handleRequestDelete = this.handleRequestDelete.bind(this);
this.validate = this.validate.bind(this);
this.state = { this.state = {
tags: [], tags: [],
icon: [], name: "",
title: "",
errors: {}, errors: {},
banner: [],
defValue: "", defValue: "",
category: 0, category: {},
visibility: 0, visibility: {type: "PUBLIC", allowedList: []},
description: "", description: "",
screenshots: [],
identifier: "",
shortDescription: "" shortDescription: ""
}; };
} }
componentWillMount() {
const defaultVals = this.props.defaultData;
if (defaultVals) {
this.setState(defaultVals);
}
}
/** /**
* Create a tag on Enter key press and set it to the state. * Create a tag on Enter key press and set it to the state.
* Clears the tags text field. * Clears the tags text field.
@ -85,81 +99,222 @@ class Step1 extends Component {
* Handles Chip delete function. * Handles Chip delete function.
* Removes the tag from state.tags * Removes the tag from state.tags
* */ * */
handleRequestDelete(event) { handleRequestDelete(key) {
this.chipData = this.state.tags; let chipData = this.state.tags;
console.log(event.target); const chipToDelete = chipData.map((chip) => chip.key).indexOf(key);
const chipToDelete = this.chipData.map((chip) => chip.value).indexOf(event.target.value); chipData.splice(chipToDelete, 1);
this.chipData.splice(chipToDelete, 1); this.setState({tags: chipData});
this.setState({tags: this.chipData});
}; };
/** /**
* Creates an object with the current step data and persist in the parent. * Creates an object with the current step data and persist in the parent.
* */ * */
setStepData() { setStepData() {
let stepData = {}; const {name, description, tags, visibility, shortDescription} = this.state;
this.props.setData("step1", {step: stepData}); let stepData = {
name: name,
description: description,
tags: tags,
visibility: visibility,
shortDescription: shortDescription,
category: {id: 1, name: "business"}
};
let {errorCount, errors} = this.validate();
if (errorCount !== 0) {
this.setState({errors: errors});
} else {
this.props.setStepData("generalInfo", stepData);
}
}; };
onCancelClick() {
this.props.close();
}
/**
* Validate the form fields.
* */
validate() {
const {name, description, tags, shortDescription} = this.state;
let errorCount = 0;
let errors = {};
if (validator.validateNull(name)) {
errorCount++;
errors.name = "Application Title is Required!";
}
if (validator.validateNull(description)) {
errorCount++;
errors.description = "Description is Required!"
}
if (validator.validateNull(shortDescription)) {
errorCount++;
errors.shortDescription = "Short Description is Required!"
}
if (!validator.validateEmpty(tags)) {
errorCount++;
errors.tags = "You need to enter at least one tag!"
}
return {errorCount, errors};
}
/** /**
* Set text field values to state. * Set text field values to state.
* */ * */
onTextFieldChange(event, value) { onTextFieldChange(event) {
let field = event.target.id; let field = event.target.name;
switch (field) { switch (field) {
case "name": { case "appName": {
this.setState({name: value}); this.setState({name: event.target.value});
break;
}
case "shortDescription": {
this.setState({shortDescription: value});
break; break;
} }
case "description": { case "appDescription": {
this.setState({description: value}); this.setState({description: event.target.value});
break; break;
} }
case "identifier": { case "appShortDescription": {
this.setState({identifier: value}); this.setState({shortDescription: event.target.value});
break;
} }
} }
}; };
onVisibilityChange(event) {
console.log(event.target.value);
this.setState({visibility: event.target.value});
}
onVisibilityItemSelect(event) {
}
render() { render() {
const {visibility} = this.state;
let visibilityItem = () => {
switch (visibility) {
case("public"): {
return <div/>
}
case("roles"): {
return <FormGroup>
<Input
type="select"
name="visibility-item"
id="app-visibility-item"
onChange={this.onVisibilityItemSelect}
>
<option id="app-visibility-default" disabled selected>Select the Roles.</option>
<option><Input type="checkbox"/>Role1</option>
<option>Role2</option>
</Input>
</FormGroup>
}
case ("groups"): {
return <FormGroup>
<Input
type="select"
name="visibility-item"
id="app-visibility-item"
onChange={this.onVisibilityItemSelect}
>
<option id="app-visibility-default" disabled selected>Select the Groups.</option>
<option>Group1</option>
<option>Group2</option>
</Input>
</FormGroup>
}
default: {
return <div/>
}
}
};
return ( return (
<div className="createStep2Content"> <div>
<div> <div>
<div> <div>
<FormGroup> <FormGroup>
<Label for="app-title"> <Label for="app-title">
<FormattedMessage id='Title' defaultMessage='Title'/>* <FormattedMessage id='Title' defaultMessage='Title'/>*
</Label> </Label>
<Input required type="text" name="appName" id="app-title"/> <Input
required
type="text"
name="appName"
id="app-title"
value={this.state.name}
onChange={this.onTextFieldChange}
/>
<FormFeedback id="form-error">{this.state.errors.name}</FormFeedback>
</FormGroup>
<FormGroup>
<Label for="app-short-description">
<FormattedMessage id="shortDescription" defaultMessage="shortDescription"/>*
</Label>
<Input
required
type="textarea"
name="appShortDescription"
id="app-short-description"
value={this.state.shortDescription}
onChange={this.onTextFieldChange}
/>
<FormFeedback id="form-error">{this.state.errors.shortDescription}</FormFeedback>
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Label for="app-description"> <Label for="app-description">
<FormattedMessage id='Description' defaultMessage='Description'/>* <FormattedMessage id='Description' defaultMessage='Description'/>*
</Label> </Label>
<Input required type="textarea" name="appDescription" id="app-description"/> <Input
required
type="textarea"
name="appDescription"
id="app-description"
value={this.state.description}
onChange={this.onTextFieldChange}
/>
<FormFeedback id="form-error">{this.state.errors.description}</FormFeedback>
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Label for="app-category"> <Label for="app-category">
<FormattedMessage id='Category' defaultMessage='Category'/> <FormattedMessage id='Category' defaultMessage='Category'/>
</Label> </Label>
<Input type="select" name="category" id="app-category"> <Input
<option>Business</option> type="select"
name="category"
id="app-category"
>
<option key={0} value={{id: 0, name: "business"}}>Business</option>
</Input> </Input>
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Label for="app-visibility"> <Label for="app-visibility">
<FormattedMessage id='Visibility' defaultMessage='Visibility'/> <FormattedMessage id='Visibility' defaultMessage='Visibility'/>
</Label> </Label>
<Input type="select" name="visibility" id="app-visibility"> <Form inline>
<option><FormattedMessage id='Devices' defaultMessage='Devices'/></option> <FormGroup>
<option><FormattedMessage id='Roles' defaultMessage='Roles'/></option> <Input
<option><FormattedMessage id='Groups' defaultMessage='Groups'/></option> type="select"
</Input> name="visibility"
id="app-visibility"
onChange={this.onVisibilityChange}
>
<option id="app-visibility-default" disabled selected>Select the App Visibility
Option.
</option>
<option key={1}><FormattedMessage id='Devices' defaultMessage='Devices'/>
</option>
<option key={2}><FormattedMessage id='Roles' defaultMessage='Roles'/></option>
<option key={3}><FormattedMessage id='Groups' defaultMessage='Groups'/></option>
</Input>
</FormGroup>
{visibilityItem()}
</Form>
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Label for="app-tags"><FormattedMessage id='Tags' defaultMessage='Tags'/>*</Label> <Label for="app-tags"><FormattedMessage id='Tags' defaultMessage='Tags'/>*</Label>
@ -172,23 +327,31 @@ class Step1 extends Component {
onChange={this.handleTagChange.bind(this)} onChange={this.handleTagChange.bind(this)}
onKeyPress={this.addTags.bind(this)} onKeyPress={this.addTags.bind(this)}
/> />
<div id="batch-content"> <div id="batch-content">
{this.state.tags.map(tag => { {this.state.tags.map(tag => {
return ( return (
<Badge <Chip
style={{margin: '0 2px 0 2px'}} key={tag.key}
value={tag.value} content={tag}
onClick={this.handleRequestDelete.bind(this)} onDelete={this.handleRequestDelete}
> />
{tag.value}
</Badge>
) )
} }
)} )}
</div> </div>
<FormFeedback id="form-error">{this.state.errors.tags}</FormFeedback>
</FormGroup> </FormGroup>
</div> </div>
</div> </div>
<ModalFooter>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary" onClick={this.setStepData}>
<FormattedMessage id="Continue" defaultMessage="Continue"/>
</Button>
</ModalFooter>
</div> </div>
); );
} }
@ -197,8 +360,7 @@ class Step1 extends Component {
Step1.prototypes = { Step1.prototypes = {
handleNext: PropTypes.func, handleNext: PropTypes.func,
handlePrev: PropTypes.func, handlePrev: PropTypes.func,
setData: PropTypes.func, setData: PropTypes.func
removeData: PropTypes.func
}; };
export default Step1; export default Step1;

@ -20,8 +20,9 @@ import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import AuthHandler from "../../../../api/authHandler"; import AuthHandler from "../../../../api/authHandler";
import PlatformMgtApi from "../../../../api/platformMgtApi"; import PlatformMgtApi from "../../../../api/platformMgtApi";
import {FormGroup, Input, Label} from 'reactstrap'; import {Button, FormFeedback, FormGroup, Input, Label, ModalFooter} from 'reactstrap';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import * as validator from '../../../../common/validator';
/** /**
* The first step of the application creation wizard. * The first step of the application creation wizard.
@ -41,24 +42,30 @@ class Step2 extends Component {
super(); super();
this.setPlatforms = this.setPlatforms.bind(this); this.setPlatforms = this.setPlatforms.bind(this);
this.setStepData = this.setStepData.bind(this); this.setStepData = this.setStepData.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.onBackClick = this.onBackClick.bind(this);
this.validate = this.validate.bind(this);
this.platforms = []; this.platforms = [];
this.state = { this.state = {
finished: false, errors: {},
stepIndex: 0,
store: 1, store: 1,
platformSelectedIndex: 0, platformSelectedIndex: 0,
platform: "", platform: {},
platforms: [], platforms: []
stepData: [],
title: "",
titleError: ""
}; };
} }
componentWillMount() {
const {defaultData} = this.props;
if (defaultData) {
this.setState(defaultData);
}
}
componentDidMount() { componentDidMount() {
//Get the list of available platforms and set to the state. //Get the list of available platforms and set to the state.
PlatformMgtApi.getPlatforms().then(response => { PlatformMgtApi.getPlatforms().then(response => {
console.log(response);
this.setPlatforms(response.data); this.setPlatforms(response.data);
}).catch(err => { }).catch(err => {
AuthHandler.unauthorizedErrorHandler(err); AuthHandler.unauthorizedErrorHandler(err);
@ -76,25 +83,51 @@ class Step2 extends Component {
platform = platforms[index]; platform = platforms[index];
tmpPlatforms.push(platform); tmpPlatforms.push(platform);
} }
this.setState({platforms: tmpPlatforms, platformSelectedIndex: 0, platform: tmpPlatforms[0].name}) this.setState({platforms: tmpPlatforms, platformSelectedIndex: 0})
} }
/** /**
* Persist the current form data to the state. * Persist the current form data to the state.
* */ * */
setStepData() { setStepData() {
let step = { const {store, platform} = this.state;
store: this.state.store, let data = {
platform: this.state.platforms[this.state.platformSelectedIndex] store: store,
platform: platform[0]
}; };
this.props.setData("step2", {step: step});
const {errorCount, errors} = this.validate();
if (errorCount > 0) {
this.setState({errors: errors})
} else {
this.props.setStepData("platform", data);
}
}
onCancelClick() {
this.props.close();
}
onBackClick() {
this.props.handlePrev();
}
validate() {
const {store, platform} = this.state;
let errors = {};
let errorCount = 0;
if (!validator.validateEmptyObject(platform)) {
errorCount++;
errors.platform = "You must select an application platform!"
}
return {errorCount, errors};
} }
/** /**
* Triggers when changing the Platform selection. * Triggers when changing the Platform selection.
* */ * */
onChangePlatform(event) { onChangePlatform(event) {
console.log(event.target.value, this.state.platforms);
let id = event.target.value; let id = event.target.value;
let selectedPlatform = this.state.platforms.filter((platform) => { let selectedPlatform = this.state.platforms.filter((platform) => {
return platform.identifier === id; return platform.identifier === id;
@ -122,16 +155,34 @@ class Step2 extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Label for="store"><FormattedMessage id='Platform' defaultMessage='Platform'/></Label> <Label for="store"><FormattedMessage id='Platform' defaultMessage='Platform'/></Label>
<Input type="select" name="store" onChange={this.onChangePlatform.bind(this)}> <Input
required
type="select"
name="store"
onChange={this.onChangePlatform.bind(this)}
>
<option id="app-visibility-default" disabled selected>Select the Application Platform</option>
{this.state.platforms.length > 0 ? this.state.platforms.map(platform => { {this.state.platforms.length > 0 ? this.state.platforms.map(platform => {
return ( return (
<option value={platform.identifier}> <option value={platform.identifier} key={platform.identifier}>
{platform.name} {platform.name}
</option> </option>
) )
}) : <option><FormattedMessage id='No.Platform' defaultMessage='No Platforms'/></option>} }) : <option><FormattedMessage id='No.Platform' defaultMessage='No Platforms'/></option>}
</Input> </Input>
<FormFeedback id="form-error">{this.state.errors.platform}</FormFeedback>
</FormGroup> </FormGroup>
<ModalFooter>
<Button className="custom-flat primary-flat" onClick={this.onBackClick}>
<FormattedMessage id="Back" defaultMessage="Back"/>
</Button>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary" onClick={this.setStepData}>
<FormattedMessage id="Continue" defaultMessage="Continue"/>
</Button>
</ModalFooter>
</div> </div>
); );
} }

@ -19,7 +19,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Dropzone from 'react-dropzone'; import Dropzone from 'react-dropzone';
import React, {Component} from 'react'; import React, {Component} from 'react';
import {FormGroup, Label} from 'reactstrap'; import * as validator from '../../../../common/validator';
import {Button, FormFeedback, FormGroup, Label, ModalFooter} from 'reactstrap';
import AppImage from "../../../UIComponents/AppImage/AppImage"; import AppImage from "../../../UIComponents/AppImage/AppImage";
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
@ -40,47 +41,77 @@ import {FormattedMessage} from 'react-intl';
class Step3 extends Component { class Step3 extends Component {
constructor() { constructor() {
super(); super();
this.setStepData = this.setStepData.bind(this);
this.onBackClick = this.onBackClick.bind(this);
this.validate = this.validate.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.state = { this.state = {
tags: [],
icon: [], icon: [],
title: "",
errors: {}, errors: {},
banner: [], banner: [],
defValue: "",
category: 0,
visibility: 0,
description: "",
screenshots: [], screenshots: [],
identifier: "",
shortDescription: ""
}; };
} }
/** componentWillMount() {
* Handles Chip delete function. const {defaultData} = this.props;
* Removes the tag from state.tags
* */ this.setState(defaultData);
handleRequestDelete(event) { }
this.chipData = this.state.tags;
console.log(event.target); //TODO: Remove Console log.
const chipToDelete = this.chipData.map((chip) => chip.value).indexOf(event.target.value);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
};
/** /**
* Creates an object with the current step data and persist in the parent. * Creates an object with the current step data and persist in the parent.
* */ * */
setStepData() { setStepData() {
const {icon, banner, screenshots} = this.state;
let stepData = { let stepData = {
icon: this.state.icon, icon: icon,
banner: this.state.banner, banner: banner,
screenshots: this.state.screenshots screenshots: screenshots
}; };
this.props.setData("step2", {step: stepData}); const {errorCount, errors} = this.validate();
if (errorCount > 0) {
this.setState({errors: errors})
} else {
this.props.setStepData("screenshots", stepData);
}
}; };
onCancelClick() {
this.props.close();
}
onBackClick() {
this.props.handlePrev();
}
validate() {
const {icon, banner, screenshots} = this.state;
let errors = {}, errorCount = 0;
if (!validator.validateEmpty(icon)) {
errorCount++;
errors.icon = "You must upload an icon image!"
}
if (!validator.validateEmpty(banner)) {
errorCount++;
errors.banner = "You must upload a banner image!"
}
if (!validator.validateEmpty(screenshots)) {
errorCount++;
errors.screenshots = "You must upload at least one screenshot image!"
}
return {errorCount, errors};
}
/** /**
* Removed user uploaded banner. * Removed user uploaded banner.
* */ * */
@ -126,7 +157,6 @@ class Step3 extends Component {
onDrop={(screenshots, rejected) => { onDrop={(screenshots, rejected) => {
let tmpScreenshots = this.state.screenshots; let tmpScreenshots = this.state.screenshots;
tmpScreenshots.push(screenshots); tmpScreenshots.push(screenshots);
console.log(screenshots); //TODO: Remove this
this.setState({ this.setState({
screenshots: tmpScreenshots screenshots: tmpScreenshots
}); });
@ -135,6 +165,7 @@ class Step3 extends Component {
<i className="fw fw-add"></i> <i className="fw fw-add"></i>
</Dropzone> : <div/>} </Dropzone> : <div/>}
</div> </div>
<FormFeedback id="form-error">{this.state.errors.screenshots}</FormFeedback>
</FormGroup> </FormGroup>
</div> </div>
<div style={{display: 'flex'}}> <div style={{display: 'flex'}}>
@ -162,6 +193,7 @@ class Step3 extends Component {
<i className="fw fw-add"></i> <i className="fw fw-add"></i>
</Dropzone> : <div/>} </Dropzone> : <div/>}
</div> </div>
<FormFeedback id="form-error">{this.state.errors.icon}</FormFeedback>
</FormGroup> </FormGroup>
</div> </div>
<div style={{marginLeft: '15px'}}> <div style={{marginLeft: '15px'}}>
@ -188,9 +220,21 @@ class Step3 extends Component {
</Dropzone> : <div/> </Dropzone> : <div/>
} }
</div> </div>
<FormFeedback id="form-error">{this.state.errors.banner}</FormFeedback>
</FormGroup> </FormGroup>
</div> </div>
</div> </div>
<ModalFooter>
<Button className="custom-flat primary-flat" onClick={this.onBackClick}>
<FormattedMessage id="Back" defaultMessage="Back"/>
</Button>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary" onClick={this.setStepData}>
<FormattedMessage id="Continue" defaultMessage="Continue"/>
</Button>
</ModalFooter>
</div> </div>
); );
} }

@ -18,8 +18,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Collapse, FormGroup, Input, Label, FormText} from 'reactstrap'; import {Button, Collapse, FormGroup, FormText, Input, Label, ModalFooter} from 'reactstrap';
import Switch from '../../../UIComponents/Switch/Switch' import Switch from '../../../UIComponents/Switch/Switch'
import {FormattedMessage} from 'react-intl';
/** /**
* The Third step of application create wizard. {Application Release Step} * The Third step of application create wizard. {Application Release Step}
@ -47,9 +48,10 @@ class Step4 extends Component {
constructor() { constructor() {
super(); super();
this.handleToggle = this.handleToggle.bind(this); this.handleToggle = this.handleToggle.bind(this);
this.handlePrev = this.handlePrev.bind(this); this.onCancelClick = this.onCancelClick.bind(this);
this.handleToggle = this.handleToggle.bind(this); this.onBackClick = this.onBackClick.bind(this);
this.handleFinish = this.handleFinish.bind(this); this.handleFinish = this.handleFinish.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = { this.state = {
showForm: false, showForm: false,
releaseChannel: 1, releaseChannel: 1,
@ -66,13 +68,18 @@ class Step4 extends Component {
this.props.handleFinish(); this.props.handleFinish();
} }
/** onCancelClick() {
* Invokes Prev button click. this.props.close();
* */ }
handlePrev() {
onBackClick() {
this.props.handlePrev(); this.props.handlePrev();
} }
onSubmit() {
this.props.onSubmit();
}
/** /**
* Handles release application selection. * Handles release application selection.
* */ * */
@ -90,7 +97,7 @@ class Step4 extends Component {
<div id="app-release-switch-label"> <div id="app-release-switch-label">
<Label for="app-release-switch"> <Label for="app-release-switch">
<strong> <strong>
Add Release to Application <FormattedMessage id="Add.Release" defaultMessage="Add.Release"/>
</strong> </strong>
</Label> </Label>
</div> </div>
@ -104,17 +111,19 @@ class Step4 extends Component {
</FormGroup> </FormGroup>
<br/> <br/>
<div> <div>
<FormText color="muted"> <FormText color="muted">
<i>Info: </i> <i><FormattedMessage id="Info" defaultMessage="Info"/> </i>
Enabling this will create a release for the current Application. Enabling this will create a release for the current Application.
To upload the Application, please visit to the Release management section of To upload the Application, please visit to the Release management section of
Application Edit View. Application Edit View.
</FormText> </FormText>
</div> </div>
{/*If toggle is true, the release form will be shown.*/} {/*If toggle is true, the release form will be shown.*/}
<Collapse isOpen={this.state.showForm}> <Collapse isOpen={this.state.showForm}>
<FormGroup> <FormGroup>
<Label for="release-channel">Release Channel</Label> <Label for="release-channel">
<FormattedMessage id="Release.Channel" defaultMessage="Release.Channel"/>
</Label>
<Input <Input
type="select" type="select"
id="release-channel" id="release-channel"
@ -130,7 +139,9 @@ class Step4 extends Component {
</Input> </Input>
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Label for="version">Version*</Label> <Label for="version">
<FormattedMessage id="Version" defaultMessage="Version"/>*
</Label>
<Input <Input
type="text" type="text"
id="version input-custom" id="version input-custom"
@ -140,6 +151,17 @@ class Step4 extends Component {
</FormGroup> </FormGroup>
</Collapse> </Collapse>
</div> </div>
<ModalFooter>
<Button className="custom-flat primary-flat" onClick={this.onBackClick}>
<FormattedMessage id="Back" defaultMessage="Back"/>
</Button>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary" onClick={this.onSubmit}>
<FormattedMessage id="Finish" defaultMessage="Finish"/>
</Button>
</ModalFooter>
</div> </div>
); );
} }
@ -149,7 +171,7 @@ Step4.propTypes = {
handleFinish: PropTypes.func, handleFinish: PropTypes.func,
handlePrev: PropTypes.func, handlePrev: PropTypes.func,
setData: PropTypes.func, setData: PropTypes.func,
removeData: PropTypes.func onSubmit: PropTypes.func
}; };
export default Step4; export default Step4;

@ -89,6 +89,7 @@ class ApplicationEdit extends Component {
<FormattedMessage id="Application.Name" defaultMessage="Application Name"/> <FormattedMessage id="Application.Name" defaultMessage="Application Name"/>
</Col> </Col>
</Row> </Row>
<hr/>
<Row id="application-edit-main-container"> <Row id="application-edit-main-container">
<Col xs="3"> <Col xs="3">
<div className="tab"> <div className="tab">
@ -104,17 +105,9 @@ class ApplicationEdit extends Component {
</div> </div>
</Col> </Col>
<Col xs="9"> <Col xs="9">
<div id="app-edit-content"> {/* Application edit content */}
<Row> <div id="application-edit-content">
<Col xs="12"> {this.getTabContent(this.state.activeTab)}
<div id="application-edit-outer-content">
{/* Application edit content */}
<div id="application-edit-content">
{this.getTabContent(this.state.activeTab)}
</div>
</div>
</Col>
</Row>
</div> </div>
</Col> </Col>
</Row> </Row>

@ -17,16 +17,24 @@
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Badge, Button, FormGroup, Input, Label, Row} from 'reactstrap'; import {Button, FormGroup, Input, Label, Row} from 'reactstrap';
import Dropzone from 'react-dropzone'; import Dropzone from 'react-dropzone';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import Chip from "../../../UIComponents/Chip/Chip";
class GeneralInfo extends Component { class GeneralInfo extends Component {
constructor() { constructor() {
super(); super();
this.onTextFieldChange = this.onTextFieldChange.bind(this);
this.addTags = this.addTags.bind(this);
this.handleRequestDelete = this.handleRequestDelete.bind(this);
this.handleTagChange = this.handleTagChange.bind(this);
this.state = { this.state = {
defValue: "", defValue: "",
title: "",
description: "",
shortDescription: "",
tags: [], tags: [],
screenshots: [], screenshots: [],
icon: [], icon: [],
@ -34,6 +42,62 @@ class GeneralInfo extends Component {
} }
} }
/**
* Set text field values to state.
* */
onTextFieldChange(event) {
let field = event.target.name;
console.log(event.target.value);
switch (field) {
case "appName": {
this.setState({name: event.target.value});
break;
}
case "appDescription": {
this.setState({description: event.target.value});
break;
}
case "appShortDescription": {
this.setState({shortDescription: event.target.value});
}
}
};
/**
* Create a tag on Enter key press and set it to the state.
* Clears the tags text field.
* Chip gets two parameters: Key and value.
* */
addTags(event) {
let tags = this.state.tags;
if (event.charCode === 13) {
event.preventDefault();
tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value});
this.setState({tags, defValue: ""}, console.log(tags));
}
}
/**
* Set the value for tag.
* */
handleTagChange(event) {
let defaultValue = this.state.defValue;
defaultValue = event.target.value;
this.setState({defValue: defaultValue})
}
/**
* Handles Chip delete function.
* Removes the tag from state.tags
* */
handleRequestDelete(key) {
let chipData = this.state.tags;
const chipToDelete = chipData.map((chip) => chip.key).indexOf(key);
chipData.splice(chipToDelete, 1);
this.setState({tags: chipData});
};
//TODO: Remove Console logs. //TODO: Remove Console logs.
render() { render() {
return ( return (
@ -44,18 +108,31 @@ class GeneralInfo extends Component {
<Label for="app-title"> <Label for="app-title">
<FormattedMessage id="Title" defaultMessage="Title"/>* <FormattedMessage id="Title" defaultMessage="Title"/>*
</Label> </Label>
<Input required type="text" name="appName" id="app-title"
onChange={this.onTextFieldChange}/>
</FormGroup>
<FormGroup>
<Label for="app-short-description">
<FormattedMessage id="shortDescription" defaultMessage="shortDescription"/>*
</Label>
<Input <Input
required required
type="text" type="textarea"
name="appName" name="appShortDescription"
id="app-title" id="app-short-description"
onChange={this.onTextFieldChange}
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Label for="app-title"> <Label for="app-title">
<FormattedMessage id="Description" defaultMessage="Description"/>* <FormattedMessage id="Description" defaultMessage="Description"/>*
</Label> </Label>
<Input required type="textarea" multiline name="appName" id="app-title"/> <Input
required
type="textarea"
name="appDescription"
id="app-description"
onChange={this.onTextFieldChange}/>
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Label for="app-category"> <Label for="app-category">
@ -79,16 +156,23 @@ class GeneralInfo extends Component {
<Label for="app-tags"> <Label for="app-tags">
<FormattedMessage id="Tags" defaultMessage="Tags"/>* <FormattedMessage id="Tags" defaultMessage="Tags"/>*
</Label> </Label>
<Input required type="text" value={this.state.defValue} name="app-tags" id="app-tags"/> <Input
required
type="text"
value={this.state.defValue}
name="app-tags"
id="app-tags"
onChange={this.handleTagChange.bind(this)}
onKeyPress={this.addTags.bind(this)}
/>
<div id="batch-content"> <div id="batch-content">
{this.state.tags.map(tag => { {this.state.tags.map(tag => {
return ( return (
<Badge <Chip
style={{margin: '0 2px 0 2px'}} key={tag.key}
value={tag.value} content={tag}
> onDelete={this.handleRequestDelete}
{tag.value} />
</Badge>
) )
} }
)} )}
@ -102,12 +186,14 @@ class GeneralInfo extends Component {
<span className="image-sub-title"> (600 X 800 32 bit PNG)</span> <span className="image-sub-title"> (600 X 800 32 bit PNG)</span>
<div id="screenshot-container"> <div id="screenshot-container">
{this.state.screenshots.map((tile) => ( {this.state.screenshots.map((tile) => (
<button id="img-btn-screenshot" style={{height: '210px', width: '410px'}} <button id="img-btn-screenshot"
style={{height: '210px', width: '410px'}}
onMouseEnter={() => { onMouseEnter={() => {
console.log("Mouse Entered") console.log("Mouse Entered")
}}> }}>
{console.log(tile[0].preview)} {console.log(tile[0].preview)}
<img style={{height: '200px', width: '400px'}} src={tile[0].preview}/> <img style={{height: '200px', width: '400px'}}
src={tile[0].preview}/>
</button> </button>
))} ))}
{this.state.screenshots.length < 3 ? {this.state.screenshots.length < 3 ?
@ -140,7 +226,8 @@ class GeneralInfo extends Component {
<button onMouseEnter={() => { <button onMouseEnter={() => {
console.log("Mouse Entered") console.log("Mouse Entered")
}}> }}>
<img style={{height: '200px', width: '200px'}} src={tile.preview}/> <img style={{height: '200px', width: '200px'}}
src={tile.preview}/>
</button> </button>
))} ))}
{this.state.icon.length === 0 ? {this.state.icon.length === 0 ?
@ -167,7 +254,8 @@ class GeneralInfo extends Component {
<button onMouseEnter={() => { <button onMouseEnter={() => {
console.log("Mouse Entered") console.log("Mouse Entered")
}}> }}>
<img style={{height: '200px', width: '400px'}} src={tile.preview}/> <img style={{height: '200px', width: '400px'}}
src={tile.preview}/>
</button> </button>
))} ))}
{this.state.banner.length === 0 ? {this.state.banner.length === 0 ?
@ -186,9 +274,8 @@ class GeneralInfo extends Component {
</div> </div>
</div> </div>
<div className="save-info"> <div className="save-info">
<Button> <Button className="custom-flat danger-flat">Cancel</Button>
<FormattedMessage id="Save" defaultMessage="Save"/> <Button className="custom-raised primary">Save</Button>
</Button>
</div> </div>
</form> </form>
</Row> </Row>

@ -16,7 +16,6 @@
* under the License. * under the License.
*/ */
import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
class PackageManager extends Component { class PackageManager extends Component {
@ -26,7 +25,7 @@ class PackageManager extends Component {
} }
render() { render() {
return( return (
<div id="package-mgt-content"> <div id="package-mgt-content">
</div> </div>

@ -92,7 +92,9 @@ class CreateRelease extends Component {
</p> </p>
</div> </div>
<div> <div>
<Button id="create-release-btn" onClick={this.showUploadArtifacts}>Create a {channel} Release</Button> <Button id="create-release-btn" onClick={this.showUploadArtifacts}>Create
a {channel}
Release</Button>
</div> </div>
</div> </div>
</Row> </Row>

@ -41,7 +41,8 @@ class UploadPackage extends Component {
<a onClick={this.handleBack}>{"<-"}</a> <a onClick={this.handleBack}>{"<-"}</a>
<span id="create-release-header"> <span id="create-release-header">
<strong> <strong>
<FormattedMessage id="New.Release.For" defaultMessage="New Release For"/> {selectedChannel} <FormattedMessage id="New.Release.For"
defaultMessage="New Release For"/> {selectedChannel}
</strong> </strong>
</span> </span>
</div> </div>

@ -47,91 +47,108 @@ class ApplicationView extends Component {
} }
render() { render() {
const platform = this.state.application; if (this.state.application.length === 0) {
console.log(platform); return <div/>
} else {
const app = this.state.application;
console.log(app);
return (
<div id="application-view-content">
<div id="application-view-row">
<Row>
<Col>
<div id="app-icon">
{/*TODO: Remove this*/}
<img
className="app-view-image"
src={app.icon}
/>
</div>
</Col>
<Col>
<Row>
<p className="app-view-field">{app.name}</p>
</Row>
<Row>
<span className="app-updated-date app-view-text">
<FormattedMessage id="Last.Updated"
defaultMessage="Last.Updated"/> {app.modifiedAt}</span>
</Row>
</Col>
</Row>
</div>
<div id="application-view-row">
<Row>
<Col>
<span className="app-install-count app-view-text">
2k <FormattedMessage id="Installs" defaultMessage="Installs"/>
</span>
</Col>
</Row>
<Row>
<Col>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
</Col>
<Col>
<p className="app-view-text">
<a href="#">
<FormattedMessage id="View.In.Store" defaultMessage="View.In.Store"/>
</a>
</p>
</Col>
</Row>
</div>
<hr/>
<div id="application-view-row">
<Row>
<Col>
<p className="app-view-field">
<FormattedMessage id="Description" defaultMessage="Description"/>:
</p>
</Col>
<Col>
<p className="app-view-text">{app.description}</p>
</Col>
</Row>
<Row>
<Col>
<p className="app-view-field">
<FormattedMessage id="Tags" defaultMessage="Tags"/>:
</p>
</Col>
<Col>
<p className="app-view-text">[list of tags...]</p>
</Col>
</Row>
<Row>
<Col>
<p className="app-view-field">
<FormattedMessage id="Release" defaultMessage="Release"/>:
</p>
</Col>
<Col>
<p className="app-view-text">Production</p>
</Col>
</Row>
<Row>
<Col>
<p className="app-view-field">
<FormattedMessage id="Version" defaultMessage="Version"/>:
</p>
</Col>
<Col>
<p className="app-view-text">v1.0</p>
</Col>
</Row>
</div>
</div>
return ( );
<div id="application-view-content">
<div id="application-view-row">
<Row>
<Col>
<div id="app-icon">
</div> }
</Col>
<Col>
<Row>
<span><strong>Facebook</strong></span>
</Row>
<Row>
<span className="app-updated-date">Last updated on 2017-09-23</span>
</Row>
</Col>
</Row>
</div>
<div id="application-view-row">
<Row>
<Col>
<span className="app-install-count">2k Installs</span>
</Col>
</Row>
<Row>
<Col>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
</Col>
<Col>
<a href="#">View in Store</a>
</Col>
</Row>
</div>
<hr/>
<div id="application-view-row">
<Row>
<Col>
<span><strong>
<FormattedMessage id="Description" defaultMessage="Description"/>:
</strong></span>
</Col>
<Col>
<p>sdfjlkdsjfsjdfjsdf sfjdslkjfdsflkjdsfslkdjfl j</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>
<FormattedMessage id="Tags" defaultMessage="Tags"/>:
</strong></span>
</Col>
<Col>
<p>[list of tags...]</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>
<FormattedMessage id="Release" defaultMessage="Release"/>:
</strong></span>
</Col>
<Col>
<p>Production</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>
<FormattedMessage id="Version" defaultMessage="Version"/>:
</strong></span>
</Col>
<Col>
<p>v1.0</p>
</Col>
</Row>
</div>
</div>
);
} }
} }

@ -0,0 +1,123 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {Button, Col, Collapse, Row} from "reactstrap";
import {FormattedMessage} from "react-intl";
/**
* Platform component.
* In Platform listing, this component will be displayed as cards.
* */
class Platform extends Component {
constructor() {
super();
this.unFold = this.unFold.bind(this);
this.state = {
isOpen: false
}
}
unFold() {
let isOpen = this.state.isOpen;
this.setState({isOpen: !isOpen})
}
render() {
const {platform} = this.props;
return (
<div className="platform-content">
<Row>
<Col>
<div className="platform-text-container">
<p className="app-view-field">{platform.name}</p>
<p className="app-view-text">{platform.enabled ? "Active" : "Disabled"}</p>
</div>
</Col>
<Col>
<div className="platform-icon-container">
<p className="platform-icon-letter">{platform.name.charAt(0)}</p>
</div>
</Col>
</Row>
<Row>
<div className="platform-content-footer">
<Button className="custom-flat grey">{platform.enabled ?
<FormattedMessage id="Disable" defaultMessage="Disable"/> :
<FormattedMessage id="Activate" defaultMessage="Activate"/>}
</Button>
<Button className="custom-flat grey">
<FormattedMessage id="Share.With.Tenants" defaultMessage="Share.With.Tenants"/>
</Button>
<Button className="custom-flat grey circle-button" onClick={this.unFold}>
{this.state.isOpen ? <i className="fw fw-up"></i> : <i className="fw fw-down"></i>}
</Button>
</div>
</Row>
<div className="platform-content-more-outer">
<Row>
<Col>
<Collapse isOpen={this.state.isOpen}>
<div className="platform-content-more">
<Row>
<Col>
<p className="app-view-field">
<FormattedMessage id="Description" defaultMessage="Description"/>
</p>
</Col>
<Col><p className="app-view-text">{platform.description}</p></Col>
</Row>
<Row>
<Col>
<p className="app-view-field">
<FormattedMessage id="File.Based" defaultMessage="File.Based"/>
</p>
</Col>
<Col>
<p className="app-view-text">{platform.fileBased ?
<FormattedMessage id="Yes" defaultMessage="Yes"/>
: <FormattedMessage id="No" defaultMessage="No"/>}</p>
</Col>
</Row>
<Row>
<Col><p className="app-view-field">
<FormattedMessage id="Tags" defaultMessage="Tags"/>
</p></Col>
<Col>
<p className="app-view-text">
{platform.tags.length > 0 ?
platform.tags :
<FormattedMessage
id="No.Platform.Tags"
defaultMessage="No.Platform.Tags"/>
}
</p>
</Col>
</Row>
</div>
</Collapse>
</Col>
</Row>
</div>
</div>
);
}
}
export default Platform;

@ -15,416 +15,69 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import PropTypes from 'prop-types';
import Chip from 'material-ui/Chip';
import Dropzone from 'react-dropzone';
import React, {Component} from 'react'; import React, {Component} from 'react';
import Toggle from 'material-ui/Toggle'; import {Button, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader} from "reactstrap";
import MenuItem from 'material-ui/MenuItem'; import {FormattedMessage} from "react-intl";
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import IconButton from 'material-ui/IconButton';
import SelectField from 'material-ui/SelectField';
import RaisedButton from 'material-ui/RaisedButton';
import PlatformMgtApi from '../../api/platformMgtApi';
import Clear from 'material-ui/svg-icons/content/clear';
import {GridList, GridTile} from 'material-ui/GridList';
import Close from 'material-ui/svg-icons/navigation/close';
import {Card, CardActions, CardTitle} from 'material-ui/Card';
import AddCircleOutline from 'material-ui/svg-icons/content/add-circle-outline';
/** /**
* Platform Create component. * Platform view component.
* Contains following components:
* * Platform Name
* * Platform Description
* * Platform Icon
* * Whether the platform needs an app to be installed.
* * Whether the platform is enabled by default.
* * Whether the platform is shared with tenants.
* */ * */
class PlatformCreate extends Component { class PlatformCreate extends Component {
constructor() { constructor() {
super(); super();
this.onCreatePlatform = this.onCreatePlatform.bind(this); this.onCancelClick = this.onCancelClick.bind(this);
this.handleToggle = this.handleToggle.bind(this);
this.addProperty = this.addProperty.bind(this);
this.addTags = this.addTags.bind(this);
this.clearForm = this.clearForm.bind(this);
this.onPropertySelect = this.onPropertySelect.bind(this);
this.handleTagChange = this.handleTagChange.bind(this);
this.removeIcon = this.removeIcon.bind(this);
this.onTextChange = this.onTextChange.bind(this);
this.renderChip = this.renderChip.bind(this);
this.removeProperty = this.removeProperty.bind(this);
this.state = { this.state = {
tags: [], open: false
defValue: "",
enabled: true,
allTenants: false,
files: [],
platformProperties: [],
selectedProperty: 0,
name: "",
description: "",
property: "",
icon: [],
identifier: "",
propertyTypes: [
{key: 0, value: 'String'},
{key: 1, value: 'Number'},
{key: 2, value: 'Boolean'},
{key: 3, value: 'File'}]
};
}
/**
* Handles toggle button actions.
* One method is used for all the toggle buttons and, each toggle is identified by the id.
* */
handleToggle(event) {
switch (event.target.id) {
case "enabled" : {
let enabled = this.state.enabled;
this.setState({enabled: !enabled});
break;
}
case "tenant" : {
let allTenants = this.state.allTenants;
this.setState({allTenants: !allTenants});
break;
}
} }
} }
/**
* Triggers the onChange action on property type selection.
* */
onPropertySelect(event, index, value) {
console.log(this.state.propertyTypes[value]);
this.setState({selectedProperty: value});
}
/** componentWillReceiveProps(props, nextprops) {
* Handles Chip delete function. this.setState({open: props.open})
* Removes the tag from state.tags
* */
handleTagDelete(key) {
this.chipData = this.state.tags;
const chipToDelete = this.chipData.map((chip) => chip.key).indexOf(key);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
} }
/** componentWillMount() {
* Create a tag on Enter key press and set it to the state. this.setState({open: this.props.open});
* Clears the tags text field.
* Chip gets two parameters: Key and value.
* */
addTags(event) {
let tags = this.state.tags;
if (event.charCode === 13) {
event.preventDefault();
tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value});
this.setState({tags, defValue: ""});
}
} }
/** onCancelClick() {
* Creates Chip array from state.tags. this.setState({open: false})
* */
renderChip(data) {
return (
<Chip
key={data.key}
onRequestDelete={() => this.handleTagDelete(data.key)}
style={this.styles.chip}
>
{data.value}
</Chip>
);
}
/**
* Set the value for tag.
* */
handleTagChange(event) {
let defaultValue = this.state.defValue;
defaultValue = event.target.value;
this.setState({defValue: defaultValue})
}
/**
* Remove the selected property from the property list.
* */
removeProperty(property) {
let properties = this.state.platformProperties;
properties.splice(properties.indexOf(property), 1);
this.setState({platformProperties: properties});
}
/**
* Add a new platform property.
* */
addProperty() {
let property = this.state.property;
let selected = this.state.selectedProperty;
this.setState({
platformProperties:
this.state.platformProperties.concat([
{
key: property,
value: this.state.propertyTypes[selected].value
}]),
property: "",
selectedProperty: 0
});
}
/**
* Triggers in onChange event of text fields.
* Text fields are identified by their ids and the value will be persisted in the component state.
* */
onTextChange(event, value) {
let property = this.state.property;
let name = this.state.name;
let description = this.state.description;
let identifier = this.state.identifier;
switch (event.target.id) {
case "name": {
name = value;
this.setState({name: name});
break;
}
case "description": {
description = value;
this.setState({description: description});
break;
}
case "property": {
property = value;
this.setState({property: property});
break;
}
case "identifier": {
identifier = value;
this.setState({identifier: identifier});
}
}
};
/**
* Create platform object and call the create platform api.
* */
onCreatePlatform(event) {
//Call the platform create api.
event.preventDefault();
let platform = {};
platform.identifier = this.state.identifier;
platform.name = this.state.name;
platform.description = this.state.description;
platform.tags = this.state.tags;
platform.properties = this.state.platformProperties;
platform.icon = this.state.icon;
platform.enabled = this.state.enabled;
platform.allTenants = this.state.allTenants;
platform.defaultTenantMapping = true;
PlatformMgtApi.createPlatform(platform);
}
/**
* Remove the uploaded icon.
* */
removeIcon(event) {
event.preventDefault();
this.setState({icon: []});
}
/**
* Clears the user entered values in the form.
* */
clearForm(event) {
event.preventDefault();
this.setState({
enabled: true,
allTenants: false,
files: [],
platformProperties: [],
selectedProperty: 0,
name: "",
description: "",
property: "",
})
} }
render() { render() {
const {
platformProperties,
allTenants,
enabled,
selectedProperty,
propertyTypes,
name,
tags,
defValue,
description,
identifier,
property
} = this.state;
return ( return (
<div className="middle createplatformmiddle"> <div>
<Card> <Modal isOpen={this.state.open} toggle={this.toggle} id="platform-create-modal" backdrop={'static'}>
<CardTitle title="Create Platform"/> <ModalHeader>
<CardActions> <FormattedMessage id="Create.Platform" defaultMessage="Create.Platform"/>
<div className="createplatformcardaction"> </ModalHeader>
<form> <ModalBody>
<TextField <FormGroup>
hintText="Unique Identifier for Platform." <Label for="platform-name">
id="identifier" <FormattedMessage id="Name" defaultMessage="Name"/>*
floatingLabelText="Identifier*" </Label>
floatingLabelFixed={true} <Input required type="text" name="appName" id="platform-name"/>
value={identifier} </FormGroup>
onChange={this.onTextChange} <FormGroup>
/> <Label for="platform-description">
<br/> <FormattedMessage id="Description" defaultMessage="Description"/>*
<TextField </Label>
hintText="Enter the Platform Name." <Input required type="textarea" name="appName" id="platform-description"/>
id="name" </FormGroup>
floatingLabelText="Name*" </ModalBody>
floatingLabelFixed={true} <ModalFooter>
value={name} <Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
onChange={this.onTextChange} <FormattedMessage id="Cancel" defaultMessage="Cancel"/>
/> </Button>
<br/> <Button className="custom-raised primary">
<TextField <FormattedMessage id="Create" defaultMessage="Create"/>
id="description" </Button>
hintText="Enter the Platform Description." </ModalFooter>
floatingLabelText="Description*" </Modal>
floatingLabelFixed={true}
multiLine={true}
rows={2}
value={description}
onChange={this.onTextChange}
/>
<br/>
<br/>
<Toggle
id="tenant"
label="Shared with all Tenants"
labelPosition="right"
onToggle={this.handleToggle}
toggled={allTenants}
/>
<br/>
<Toggle
id="enabled"
label="Enabled"
labelPosition="right"
onToggle={this.handleToggle}
toggled={enabled}
/>
<br/>
<TextField
id="tags"
hintText="Enter Platform tags.."
floatingLabelText="Tags*"
floatingLabelFixed={true}
value={defValue}
onChange={this.handleTagChange}
onKeyPress={this.addTags}
/>
<br/>
<div className="createPlatformTagWrapper">
{tags.map(this.renderChip, this)}
</div>
<br/>
<div>
<p className="createplatformproperties">Platform Properties</p>
<div id="property-container">
{platformProperties.map((p) => {
return <div key={p.key}>{p.key} : {p.value}
<IconButton onClick={this.removeProperty.bind(this, p)}>
<Close className="createplatformpropertyclose"/>
</IconButton>
</div>
})}
</div>
<div className="createplatformproperty">
<TextField
id="property"
hintText="Property Name"
floatingLabelText="Platform Property*"
floatingLabelFixed={true}
value={this.state.property}
onChange={this.onTextChange}
/> <em/>
<SelectField
className="createplatformpropertyselect"
floatingLabelText="Property Type"
value={selectedProperty}
floatingLabelFixed={true}
onChange={this.onPropertySelect}>
{propertyTypes.map((type) => {
return <MenuItem key={type.key}
value={type.key}
primaryText={type.value}/>
})}
</SelectField>
<IconButton onClick={this.addProperty}>
<AddCircleOutline/>
</IconButton>
<br/>
</div>
</div>
<div>
<p className="createplatformiconp">Platform Icon*:</p>
<GridList className="createplatformicon" cols={1.1}>
{this.state.icon.map((tile) => (
<GridTile
key={Math.floor(Math.random() * 1000)}
title={tile.name}
actionIcon={
<IconButton onClick={this.removeIcon}>
<Clear/>
</IconButton>}>
<img src={tile.preview}/>
</GridTile>
))}
{this.state.icon.length === 0 ?
<Dropzone
className="createplatformdropzone"
accept="image/jpeg, image/png"
onDrop={(icon, rejected) => {
this.setState({icon, rejected})
}}
>
<p className="createplatformdropzonep">+</p>
</Dropzone> : <div/>}
</GridList>
</div>
<br/>
<RaisedButton
primary={true} label="Create"
onClick={this.onCreatePlatform}/>
<FlatButton label="Cancel" onClick={this.clearForm}/>
</form>
</div>
</CardActions>
</Card>
</div> </div>
); );
} }
} }
PlatformCreate.prototypes = {};
export default PlatformCreate; export default PlatformCreate;

@ -15,145 +15,63 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import {withRouter} from 'react-router-dom'; import {Button, Col, Row} from "reactstrap";
import TextField from 'material-ui/TextField'; import Platform from "./Platform";
import AuthHandler from "../../api/authHandler";
import DataTable from '../UIComponents/DataTable/DataTable';
import PlatformMgtApi from "../../api/platformMgtApi"; import PlatformMgtApi from "../../api/platformMgtApi";
import {Card, CardActions, CardTitle} from 'material-ui/Card'; import AuthHandler from "../../api/authHandler";
import PlatformCreate from "./PlatformCreate";
/** /**
* The App Create Component. * Platform view component.
*
* Application creation is handled through a Wizard. (We use Material UI Stepper.)
*
* In each step, data will be set to the state separately.
* When the wizard is completed, data will be arranged and sent to the api.
* */ * */
class PlatformListing extends Component { class PlatformListing extends Component {
constructor() { constructor() {
super(); super();
this.setPlatforms = this.setPlatforms.bind(this); this.onPlatformCreateClick = this.onPlatformCreateClick.bind(this);
this.state = { this.state = {
platforms: [], platforms: [],
asc: true openModal: false
};
}
headers = [
{
data_id: "image",
data_type: "image",
sortable: false,
label: ""
},
{
data_id: "platformName",
data_type: String,
sortable: true,
label: "Platform Name",
sort: this.sortData
},
{
data_id: "enabled",
data_type: String,
sortable: false,
label: "Enabled"
},
{
data_id: "fileBased",
data_type: String,
sortable: false,
label: "File Based"
}
];
componentDidMount() {
let platformsPromise = PlatformMgtApi.getPlatforms();
platformsPromise.then(
response => {
let platforms = this.setPlatforms(response.data);
this.setState({platforms: platforms});
}
).catch(
err => {
AuthHandler.unauthorizedErrorHandler(err);
}
)
}
/**
* Create platform objects from the response which can be displayed in the table.
* */
setPlatforms(platforms) {
let tmpPlatforms = [];
for (let index in platforms) {
let platform = {};
platform.id = platforms[index].identifier;
platform.platformName = platforms[index].name;
platform.enabled = platforms[index].enabled.toString();
platform.fileBased = platforms[index].fileBased.toString();
tmpPlatforms.push(platform)
} }
return tmpPlatforms;
}
/**
* Handles the search action.
* When typing in the search bar, this method will be invoked.
* */
searchApplications(word) {
let searchedData = [];
}
/**
* Handles sort data function and toggles the asc state.
* asc: true : sort in ascending order.
* */
sortData() {
let isAsc = this.state.asc;
let datas = isAsc ? this.data.sort(this.compare) : this.data.reverse();
this.setState({data: datas, asc: !isAsc});
} }
compare(a, b) { componentWillMount() {
if (a.applicationName < b.applicationName) PlatformMgtApi.getPlatforms().then(response => {
return -1; console.log(response);
if (a.applicationName > b.applicationName) this.setState({platforms: response.data});
return 1; }).catch(err => {
return 0; AuthHandler.unauthorizedErrorHandler(err);
})
} }
onRowClick(id) { onPlatformCreateClick() {
//TODO: Remove this this.setState({openModal: true});
console.log(id)
} }
render() { render() {
return ( return (
<div className='middle listingplatformmiddle'> <div id="platform-listing">
<Card className='listingplatformcard'> <Row>
<TextField hintText="Search" onChange={this.searchApplications.bind(this)} <div className="create-platform">
className='listingplatformsearch'/> <Button className="custom-flat grey" onClick={this.onPlatformCreateClick}>
<CardTitle title="Platforms" className='listingplatformTitle'/> <i className="fw fw-add"></i>Create Platform
<CardActions> </Button>
</div>
</CardActions> </Row>
<DataTable <Row>
headers={this.headers} <div id="platform-list">
data={this.state.platforms} {this.state.platforms.map(platform => {
handleRowClick={this.onRowClick.bind(this)} return (
noDataMessage={{type: 'button', text: 'Create Platform'}}/> <Platform key={platform.identifier} platform={platform}/>
</Card> )
})}
</div>
</Row>
<PlatformCreate open={this.state.openModal}/>
</div> </div>
); );
} }
} }
PlatformListing.propTypes = {}; export default PlatformListing;
export default withRouter(PlatformListing);

@ -18,7 +18,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import Theme from '../../../theme'; import './appImage.css';
/** /**
* Component for holding uploaded image. * Component for holding uploaded image.
@ -29,18 +29,6 @@ class AppImage extends Component {
constructor() { constructor() {
super(); super();
this.removeImage = this.removeImage.bind(this); this.removeImage = this.removeImage.bind(this);
this.scriptId = "appImage";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
} }
/** /**
@ -56,7 +44,7 @@ class AppImage extends Component {
const {image, imageId} = this.props; const {image, imageId} = this.props;
return ( return (
<div className="image-container" style={this.props.imageStyles}> <div className="image-container" style={this.props.imageStyles}>
<img src={image} className="image" id={imageId}/> <img src={image} style={{width: '100%'}} className="image" id={imageId}/>
<div className="btn-content"> <div className="btn-content">
<i className="close-btn" id={imageId} onClick={this.removeImage}>X</i> <i className="close-btn" id={imageId} onClick={this.removeImage}>X</i>
</div> </div>

@ -16,35 +16,38 @@
* under the License. * under the License.
*/ */
import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import Theme from '../../../theme'; import './chip.css';
class Chip extends Component { class Chip extends Component {
constructor() { constructor() {
super(); super();
this.scriptId = "chip"; this.onDeleteClick = this.onDeleteClick.bind(this);
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
} }
componentWillUnmount() { onDeleteClick() {
Theme.removeThemingScripts(this.scriptId); this.props.onDelete(this.props.content.key);
} }
render() { render() {
return ( return (
<div className="chip"> <div className="chip">
{this.props.image?<img src={this.props.image} alt="Person" width="96" height="96" />:<div/>} <div className="chip-content">
{this.props.text} <div className="chip-text">{this.props.content.value}</div>
<span className="close-btn" >&times;</span> <div className="chip-close-btn" onClick={this.onDeleteClick}>
<i className="fw fw-error"></i>
</div>
</div>
</div> </div>
) )
} }
} }
Chip.propTypes = {
onDelete: PropTypes.func,
content: PropTypes.object
};
export default Chip; export default Chip;

@ -0,0 +1,70 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Chip component with the material design specs.
* Chip with a close button.
* (-12px-{text (Roboto-Regular 13px)}-4px-{close button}-4px-)
*/
.chip {
margin-right: 5px;
height: 32px;
background-color: #f1f1f1;
border-radius: 16px;
border: 10px;
}
.chip-content {
display: flex;
margin: 4px 4px 4px 12px;
}
.chip-text {
height: 24px;
font-family: "Roboto-Regular";
font-size: 13px;
font-weight: 400;
color: rgba(0, 0, 0, 0.87);
line-height: 20px;
padding-top: 4px;
margin-right: 4px;
user-select: none;
white-space: nowrap;
}
.chip-close-btn {
align-content: center;
padding-left: 4px;
padding-top: 1px;
display: inline-block;
height: 24px;
width: 24px;
user-select: none;
transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
cursor: pointer;
}
.chip-close-btn i {
color: #a9a9a9;
color: rgba(0, 0, 0, 0.258824);
fill: rgba(0, 0, 0, 0.258824);
}
.chip-close-btn i:hover {
color: #959595;
}

@ -16,13 +16,7 @@
* under the License. * under the License.
*/ */
import Theme from '../../../theme';
import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import DataTableRow from './DataTableRow';
import DataTableHeader from './DataTableHeader';
import RaisedButton from 'material-ui/RaisedButton';
import {Table, TableBody, TableHeader, TableRow} from 'material-ui/Table';
/** /**
* The Custom Table Component. * The Custom Table Component.
@ -53,8 +47,6 @@ class DataTable extends Component {
constructor() { constructor() {
super(); super();
this.handleRowClick = this.handleRowClick.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.state = { this.state = {
data: [], data: [],
headers: [], headers: [],
@ -62,92 +54,21 @@ class DataTable extends Component {
this.scriptId = "data-table" this.scriptId = "data-table"
}; };
componentWillMount() {
console.log("Will mount", this.props.data); //TODO: Remove this
this.setState({data: this.props.data, headers: this.props.headers}, Theme.insertThemingScripts(this.scriptId));
/**
*Loading the theme files based on the the user-preference.
*/
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
shouldComponentUpdate(nextProps, nextState) {
if (!nextProps.data) {
this.setState({data: nextState.data});
return true;
}
this.setState({data: nextProps.data});
return true;
}
/** /**
* Triggers when user click on table row. * Triggers when user click on table row.
* This method invokes the parent method handleRowClick, which is passed via props. * This method invokes the parent method handleRowClick, which is passed via props.
* */ * */
handleRowClick(id) {
this.props.handleRowClick(id);
}
handleBtnClick(id) {
this.props.handleButtonClick(id);
}
render() { render() {
const {data, headers} = this.state; return (
<div className="data-table">
//TODO: Remove this {this.props.children}
console.log(data); </div>
)
let noDataContent = null;
if (this.props.noDataMessage.type === 'button') {
noDataContent = <div><RaisedButton label={this.props.noDataMessage.text}/></div>
}
if (data) {
return (<Table
selectable={false}>
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
<TableRow>
{headers.map((header) => {
return (
<DataTableHeader
key={header.data_id}
className="datatableRowColumn"
header={header}
/>
)}
)}
</TableRow>
</TableHeader>
<TableBody>
{data.map((dataItem) => {
return (
<DataTableRow
key={dataItem.id}
dataItem={dataItem}
handleButtonClick={this.handleBtnClick}
handleClick={this.handleRowClick}
/>
)
})}
</TableBody>
</Table>)
}
return (<div>{noDataContent}</div>);
} }
} }
DataTable.prototypes = { DataTable.prototypes = {};
data: PropTypes.arrayOf(Object),
headers: PropTypes.arrayOf(Object),
sortData: PropTypes.func,
handleRowClick: PropTypes.func,
noDataMessage: PropTypes.object
};
export default DataTable; export default DataTable;

@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import FlatButton from 'material-ui/FlatButton'; import FlatButton from 'material-ui/FlatButton';
import {TableHeaderColumn} from 'material-ui/Table'; import {TableHeaderColumn} from 'material-ui/Table';
import {Col, Row} from "reactstrap";
/** /**
* Data Table header component. * Data Table header component.
@ -55,33 +56,25 @@ class DataTableHeader extends Component {
} }
render() { render() {
let headerCell = null; /*margin-top: 30px
* margin-bottom: 10px
/** * */
* If the header is sortable, create a button with onClick handler.
* else create a span element with label as the table header.
* */
if (this.props.header.sortable) {
headerCell =
<FlatButton
label={this.props.header.label}
onClick={this.tableHeaderClick}
className="sortableHeaderCell"
/>
} else {
headerCell = <span className="notsortableHeaderCell">{this.props.header.label}</span>;
}
return ( return (
<TableHeaderColumn key={this.props.header.id} className="datatableHeaderColumn"> <Row className="data-table-header">
{headerCell} {this.props.headers.map(header => {
</TableHeaderColumn>
let headerStyle = header.size;
return <Col className={headerStyle}>{header.label}</Col>
})}
</Row>
); );
} }
} }
DataTableHeader.prototypes = { DataTableHeader.prototypes = {
header: PropTypes.object headers: PropTypes.array
}; };
export default DataTableHeader; export default DataTableHeader;

@ -23,6 +23,7 @@ import IconButton from 'material-ui/IconButton';
import Create from 'material-ui/svg-icons/content/create' import Create from 'material-ui/svg-icons/content/create'
import {TableRow, TableRowColumn} from 'material-ui/Table'; import {TableRow, TableRowColumn} from 'material-ui/Table';
import Avatar from 'material-ui/Avatar'; import Avatar from 'material-ui/Avatar';
import {Row} from "reactstrap";
/** /**
@ -63,43 +64,17 @@ class DataTableRow extends Component {
handleBtnClick(event) { handleBtnClick(event) {
event.stopPropagation(); event.stopPropagation();
console.log(event.target['id']) console.log(event.target['id'])
this.props.handleButtonClick(event.target['id']); this.props.onAppEditClick(event.target['id']);
} }
render() { render() {
const {dataItem} = this.state; const {dataItem} = this.state;
//height: 62px
return ( return (
<TableRow <Row className="data-table-row">
key={this.props.key}
onClick={this.handleClick.bind(this)}
>
<TableRowColumn
className="datatableRowColumn"
key={Math.random()}
>
<Avatar>{dataItem.name}</Avatar>
</TableRowColumn>
{Object.keys(dataItem).map((key) => {
if (key !== 'id') {
return (
<TableRowColumn
className="datatableRowColumn"
key={key}
>
{dataItem[key]}
</TableRowColumn>)
}
})} </Row>
<TableRowColumn
className="datatableRowColumn"
key={dataItem.id}
>
<IconButton id={dataItem.id} onClick={this.handleBtnClick.bind(this)}>
<Create id={dataItem.id}/>
</IconButton>
</TableRowColumn>
</TableRow>
); );
} }
} }

@ -0,0 +1,80 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Theme from '../../../theme';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import FlatButton from 'material-ui/FlatButton';
import {TableHeaderColumn} from 'material-ui/Table';
import {Col, Row} from "reactstrap";
/**
* Data Table header component.
* This component creates the header elements of the table.
* */
class HeaderCell extends Component {
constructor() {
super();
this.tableHeaderClick = this.tableHeaderClick.bind(this);
this.scriptId = "data-table";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* The onClick function of the table header.
* Invokes the function passed in the header object.
* */
tableHeaderClick() {
this.props.header.sort();
}
render() {
/*margin-top: 30px
* margin-bottom: 10px
* */
return (
<Row className="data-table-header">
{this.props.headers.map(header => {
let headerStyle = header.size;
return <Col className={headerStyle}>{header.label}</Col>
})}
</Row>
);
}
}
DataTableHeader.prototypes = {
headers: PropTypes.array
};
export default HeaderCell;

@ -18,7 +18,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import Theme from '../../../theme' import './drawer.css';
import {Row} from "reactstrap";
/** /**
* Custom React component for Application View. * Custom React component for Application View.
@ -28,18 +29,6 @@ class Drawer extends Component {
constructor() { constructor() {
super(); super();
this.closeDrawer = this.closeDrawer.bind(this); this.closeDrawer = this.closeDrawer.bind(this);
this.scriptId = "drawer";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
} }
/** /**

@ -25,21 +25,19 @@
right: 0%; right: 0%;
background-color: #ffffff; background-color: #ffffff;
overflow-x: hidden; /* Disable horizontal scroll */ overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 60px; /* Place content 60px from the top */ padding: 60px 0px 0 5px; /* Place content 60px from the top */
transition: 0.5s; /* 0.5 second transition effect to slide in the sidenav */ transition: 0.5s; /* 0.5 second transition effect to slide in the sidenav */
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
} }
.app-view-drawer a { .drawer-close-btn {
padding: 8px 8px 8px 32px; padding: 8px 8px 8px 32px;
text-decoration: none; text-decoration: none;
font-size: 25px;
color: #818181; color: #818181;
display: block; display: block;
transition: 0.3s transition: 0.3s
} }
/* Position and style the close button (top right corner) */ /* Position and style the close button (top right corner) */
.app-view-drawer .closebtn { .app-view-drawer .closebtn {
position: absolute; position: absolute;
@ -49,9 +47,8 @@
margin-left: 50px; margin-left: 50px;
} }
.drawer-close-btn { .drawer-close-btn i {
height: 40px; font-size: 14px;
width: 30px;
} }
.drawer-close-btn:hover { .drawer-close-btn:hover {
@ -67,7 +64,8 @@
/* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */ /* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */
@media screen and (max-height: 450px) { @media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;} .sidenav {
.sidenav a {font-size: 18px;} padding-top: 15px;
}
} }

@ -18,29 +18,13 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import Theme from '../../../theme'; import './floatingButton.css';
/** /**
* Floating Action button. * Floating Action button.
* */ * */
class FloatingButton extends Component { class FloatingButton extends Component {
constructor() {
super();
this.scriptId = "floatingButton";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
handleClick(event) { handleClick(event) {
this.props.onClick(event); this.props.onClick(event);
} }
@ -49,7 +33,9 @@ class FloatingButton extends Component {
let classes = 'btn-circle ' + this.props.className; let classes = 'btn-circle ' + this.props.className;
return ( return (
<div className={classes} onClick={this.handleClick.bind(this)}> <div className={classes} onClick={this.handleClick.bind(this)}>
<div className={classes + " btn-shade"}>
<i className="fw fw-add"></i> <i className="fw fw-add"></i>
</div>
</div> </div>
) )
} }

@ -16,35 +16,39 @@
* under the License. * under the License.
*/ */
/*
* Material design based Floating button.
*/
.btn-circle { .btn-circle {
color: white; color: white;
position: relative; position: relative;
background-color: #e65100;
border-radius: 50%; border-radius: 50%;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); text-align: center;
box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px
}
.btn-circle:hover {
cursor: pointer;
}
.btn-shade:focus {
background-color: rgba(0, 0, 0, .12);
} }
.btn-circle i { .btn-circle i {
position: absolute; height: 24px;
margin-top: 37%; width: 24px;
margin-left: 37%; font-size: 18px;
padding: 3px;
margin-top: 16px
} }
.small { .small {
height: 50px; height: 56px;
width: 50px; width: 56px;
} }
.medium { .medium {
height: 100px; height: 100px;
width: 100px; width: 100px;
} }
.btn-circle:hover {
cursor: pointer;
background-color: rgb(255, 93, 2);
}
.primary {
background-color: blue;
}

@ -18,9 +18,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, {Component} from 'react'; import React, {Component} from 'react';
import './imageUploader.css';
import Dropzone from "react-dropzone"; import Dropzone from "react-dropzone";
import {Row} from "reactstrap"; import {Row} from "reactstrap";
import Theme from '../../../theme';
class ImageUploader extends Component { class ImageUploader extends Component {
@ -29,19 +29,7 @@ class ImageUploader extends Component {
this.setImages = this.setImages.bind(this); this.setImages = this.setImages.bind(this);
this.state = { this.state = {
images: [] images: []
}; }
this.scriptId = "imageUploader";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
} }
setImages(images) { setImages(images) {

@ -0,0 +1,59 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Axios from 'axios';
const imageLocation = "/images/";
class Logo extends Component {
constructor() {
super();
this.state = {
image: ""
}
}
componentWillMount() {
let url = imageLocation + this.props.image_name;
Axios.get(url, {responseType: 'arraybuffer'}).then(
response => {
let image = "data:image/jpeg;base64," + new Buffer(response.data, 'binary').toString('base64');
this.setState({image: image});
}
).catch(err => {
console.log(err);
});
}
render() {
return (
<img className={this.props.className} src={this.state.image} />
)
}
}
Logo.prototypes = {
className: PropTypes.string,
image_name: PropTypes.string
};
export default Logo;

@ -18,23 +18,11 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Col, Row} from "reactstrap"; import {Col, Row} from "reactstrap";
import Theme from '../../../theme' import './notification.css';
class NotificationItem extends Component { class NotificationItem extends Component {
constructor() { constructor() {
super(); super();
this.scriptId = "notification";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
} }
render() { render() {

@ -19,23 +19,10 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Col, Row} from "reactstrap"; import {Col, Row} from "reactstrap";
import './notification.css'; import './notification.css';
import Theme from '../../../theme'
class NotificationView extends Component { class NotificationView extends Component {
constructor() { constructor() {
super(); super();
this.scriptId = "notification";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
} }
render() { render() {

@ -17,26 +17,10 @@
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import Theme from '../../../theme'; import './switch.css';
class Switch extends Component { class Switch extends Component {
constructor() {
super();
this.scriptId = "switch";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() { render() {
const {height, width} = this.props; const {height, width} = this.props;
return ( return (

@ -20,7 +20,8 @@ import qs from 'qs';
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Redirect, Switch} from 'react-router-dom'; import {Redirect, Switch} from 'react-router-dom';
import AuthHandler from '../../../api/authHandler'; import AuthHandler from '../../../api/authHandler';
import {Button, Card, CardBlock, CardTitle, Col, Form, FormGroup, Input, Label} from 'reactstrap'; import {Button, Col, Form, FormGroup, Input, Label, Row} from 'reactstrap';
import Logo from "../../UIComponents/Logo/Logo";
/** /**
* The Login Component. * The Login Component.
@ -39,7 +40,8 @@ class Login extends Component {
userName: "", userName: "",
password: "", password: "",
rememberMe: true, rememberMe: true,
errors: {} errors: {},
loginError: ""
} }
} }
@ -120,7 +122,12 @@ class Login extends Component {
loginPromis.then(response => { loginPromis.then(response => {
console.log(AuthHandler.getUser()); console.log(AuthHandler.getUser());
this.setState({isLoggedIn: AuthHandler.getUser()}); this.setState({isLoggedIn: AuthHandler.getUser()});
}) }).catch(
err => {
console.log(err);
this.setState({loginError: err});
}
);
} }
} }
@ -130,33 +137,51 @@ class Login extends Component {
return ( return (
<div id="login-container"> <div id="login-container">
{/*TODO: Style the components.*/} {/*TODO: Style the components.*/}
<Card id="login-card"> <div id="login-card">
<CardBlock> <div id="login-card-content">
<CardTitle>WSO2 IoT APP Publisher</CardTitle> <Row className="login-header">
<Form onSubmit={this.handleLogin.bind(this)}> <Col>
<FormGroup row> <Logo className="login-header-logo" image_name="logo.png"/>
<Label for="userName" sm={2}>User Name:</Label> </Col>
<Col sm={10}> <Col>
<Input type="text" name="userName" id="userName" placeholder="User Name" <p className="login-header-title">IoT APP Publisher</p>
onChange={this.onUserNameChange.bind(this)}/> </Col>
</Col> </Row>
<Row className="login-form">
</FormGroup> <Col>
<FormGroup row> <Form onSubmit={this.handleLogin.bind(this)}>
<Label for="password" sm={2}>Password:</Label> <FormGroup>
<Col sm={10}> <Label for="userName">User Name:</Label>
<Input type="password" name="text" id="password" placeholder="Password" <Input
onChange={this.onPasswordChange.bind(this)}/> type="text"
</Col> name="userName"
</FormGroup> id="userName"
<FormGroup check row> placeholder="User Name"
<Col sm={{size: 10, offset: 2}}> onChange={this.onUserNameChange.bind(this)}/>
<Button type="submit" id="login-btn">Login</Button> </FormGroup>
</Col> <FormGroup>
</FormGroup> <Label for="password">Password:</Label>
</Form> <Input
</CardBlock> valid={false}
</Card> type="password"
name="text"
id="password"
placeholder="Password"
onChange={this.onPasswordChange.bind(this)}/>
</FormGroup>
<FormGroup>
<Button
type="submit"
className="custom-raised login-btn primary"
>
Login
</Button>
</FormGroup>
</Form>
</Col>
</Row>
</div>
</div>
</div>); </div>);
} else { } else {
return ( return (

@ -21,7 +21,7 @@ import Publisher from './App';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/css/bootstrap.css';
import registerServiceWorker from './registerServiceWorker'; import registerServiceWorker from './registerServiceWorker';
import {IntlProvider, addLocaleData, defineMessages} from 'react-intl'; import {addLocaleData, defineMessages, IntlProvider} from 'react-intl';
import Axios from 'axios'; import Axios from 'axios';
import Constants from './common/constants'; import Constants from './common/constants';
import Configuration from './common/configuration'; import Configuration from './common/configuration';
@ -45,7 +45,7 @@ function loadPublisher() {
registerServiceWorker(); registerServiceWorker();
}).catch(error => { }).catch(error => {
addLocaleData(require('react-intl/locale-data/en')); addLocaleData(require('react-intl/locale-data/en'));
let defaultLocale = axios.create({ let defaultLocale = Axios.create({
baseURL: Configuration.hostConstants.baseURL + "/" + Configuration.hostConstants.appContext + "/locales" baseURL: Configuration.hostConstants.baseURL + "/" + Configuration.hostConstants.appContext + "/locales"
+ Constants.defaultLocale + ".json" + Constants.defaultLocale + ".json"
}).get(); }).get();

@ -16,6 +16,7 @@
* under the License. * under the License.
*/ */
var path = require('path'); var path = require('path');
import '!!style-loader!css-loader!src/css/font-wso2.css';
const config = { const config = {
entry: { entry: {

Loading…
Cancel
Save