Format CSS and TSX files.

This commit is contained in:
Paulo Gustavo Veiga 2022-07-09 20:56:01 -07:00
parent 0f4a8ee087
commit ae02780a1a
56 changed files with 17369 additions and 4610 deletions

View File

@ -13,7 +13,7 @@
width: 100px; width: 100px;
height: 100px; height: 100px;
cursor: crosshair; cursor: crosshair;
background-image: url("../img/bootstrap-colorpicker/saturation.png"); background-image: url('../img/bootstrap-colorpicker/saturation.png');
} }
.colorpicker-saturation i { .colorpicker-saturation i {
@ -26,8 +26,8 @@
margin: -4px 0 0 -4px; margin: -4px 0 0 -4px;
border: 1px solid #000; border: 1px solid #000;
-webkit-border-radius: 5px; -webkit-border-radius: 5px;
-moz-border-radius: 5px; -moz-border-radius: 5px;
border-radius: 5px; border-radius: 5px;
} }
.colorpicker-saturation i b { .colorpicker-saturation i b {
@ -36,8 +36,8 @@
height: 5px; height: 5px;
border: 1px solid #fff; border: 1px solid #fff;
-webkit-border-radius: 5px; -webkit-border-radius: 5px;
-moz-border-radius: 5px; -moz-border-radius: 5px;
border-radius: 5px; border-radius: 5px;
} }
.colorpicker-hue, .colorpicker-hue,
@ -64,12 +64,12 @@
} }
.colorpicker-hue { .colorpicker-hue {
background-image: url("../img/bootstrap-colorpicker/hue.png"); background-image: url('../img/bootstrap-colorpicker/hue.png');
} }
.colorpicker-alpha { .colorpicker-alpha {
display: none; display: none;
background-image: url("../img/bootstrap-colorpicker/alpha.png"); background-image: url('../img/bootstrap-colorpicker/alpha.png');
} }
.colorpicker { .colorpicker {
@ -80,8 +80,8 @@
padding: 4px; padding: 4px;
margin-top: 1px; margin-top: 1px;
-webkit-border-radius: 4px; -webkit-border-radius: 4px;
-moz-border-radius: 4px; -moz-border-radius: 4px;
border-radius: 4px; border-radius: 4px;
*zoom: 1; *zoom: 1;
} }
@ -89,7 +89,7 @@
.colorpicker:after { .colorpicker:after {
display: table; display: table;
line-height: 0; line-height: 0;
content: ""; content: '';
} }
.colorpicker:after { .colorpicker:after {
@ -135,7 +135,7 @@
height: 10px; height: 10px;
margin-top: 5px; margin-top: 5px;
clear: both; clear: both;
background-image: url("../img/bootstrap-colorpicker/alpha.png"); background-image: url('../img/bootstrap-colorpicker/alpha.png');
background-position: 0 100%; background-position: 0 100%;
} }
@ -194,11 +194,11 @@
} }
.colorpicker.colorpicker-horizontal .colorpicker-hue { .colorpicker.colorpicker-horizontal .colorpicker-hue {
background-image: url("../img/bootstrap-colorpicker/hue-horizontal.png"); background-image: url('../img/bootstrap-colorpicker/hue-horizontal.png');
} }
.colorpicker.colorpicker-horizontal .colorpicker-alpha { .colorpicker.colorpicker-horizontal .colorpicker-alpha {
background-image: url("../img/bootstrap-colorpicker/alpha-horizontal.png"); background-image: url('../img/bootstrap-colorpicker/alpha-horizontal.png');
} }
.colorpicker.colorpicker-hidden { .colorpicker.colorpicker-hidden {

View File

@ -6,4 +6,180 @@
* Licensed under the Apache License v2.0 * Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt * http://www.apache.org/licenses/LICENSE-2.0.txt
* *
*/.colorpicker-saturation{float:left;width:100px;height:100px;cursor:crosshair;background-image:url("../img/bootstrap-colorpicker/saturation.png")}.colorpicker-saturation i{position:absolute;top:0;left:0;display:block;width:5px;height:5px;margin:-4px 0 0 -4px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-saturation i b{display:block;width:5px;height:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-hue,.colorpicker-alpha{float:left;width:15px;height:100px;margin-bottom:4px;margin-left:4px;cursor:row-resize}.colorpicker-hue i,.colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:100%;height:1px;margin-top:-1px;background:#000;border-top:1px solid #fff}.colorpicker-hue{background-image:url("../img/bootstrap-colorpicker/hue.png")}.colorpicker-alpha{display:none;background-image:url("../img/bootstrap-colorpicker/alpha.png")}.colorpicker{top:0;left:0;z-index:2500;min-width:130px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1}.colorpicker:before,.colorpicker:after{display:table;line-height:0;content:""}.colorpicker:after{clear:both}.colorpicker:before{position:absolute;top:-7px;left:6px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.colorpicker:after{position:absolute;top:-6px;left:7px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url("../img/bootstrap-colorpicker/alpha.png");background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-element .input-group-addon i{display:block;width:16px;height:16px;cursor:pointer}.colorpicker.colorpicker-inline{position:relative;display:inline-block;float:none}.colorpicker.colorpicker-horizontal{width:110px;height:auto;min-width:110px}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-hue,.colorpicker.colorpicker-horizontal .colorpicker-alpha{float:left;width:100px;height:15px;margin-bottom:4px;margin-left:0;cursor:col-resize}.colorpicker.colorpicker-horizontal .colorpicker-hue i,.colorpicker.colorpicker-horizontal .colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:1px;height:15px;margin-top:0;background:#fff;border:0}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url("../img/bootstrap-colorpicker/hue-horizontal.png")}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url("../img/bootstrap-colorpicker/alpha-horizontal.png")}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block} */
.colorpicker-saturation {
float: left;
width: 100px;
height: 100px;
cursor: crosshair;
background-image: url('../img/bootstrap-colorpicker/saturation.png');
}
.colorpicker-saturation i {
position: absolute;
top: 0;
left: 0;
display: block;
width: 5px;
height: 5px;
margin: -4px 0 0 -4px;
border: 1px solid #000;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.colorpicker-saturation i b {
display: block;
width: 5px;
height: 5px;
border: 1px solid #fff;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.colorpicker-hue,
.colorpicker-alpha {
float: left;
width: 15px;
height: 100px;
margin-bottom: 4px;
margin-left: 4px;
cursor: row-resize;
}
.colorpicker-hue i,
.colorpicker-alpha i {
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 1px;
margin-top: -1px;
background: #000;
border-top: 1px solid #fff;
}
.colorpicker-hue {
background-image: url('../img/bootstrap-colorpicker/hue.png');
}
.colorpicker-alpha {
display: none;
background-image: url('../img/bootstrap-colorpicker/alpha.png');
}
.colorpicker {
top: 0;
left: 0;
z-index: 2500;
min-width: 130px;
padding: 4px;
margin-top: 1px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
*zoom: 1;
}
.colorpicker:before,
.colorpicker:after {
display: table;
line-height: 0;
content: '';
}
.colorpicker:after {
clear: both;
}
.colorpicker:before {
position: absolute;
top: -7px;
left: 6px;
display: inline-block;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-left: 7px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
}
.colorpicker:after {
position: absolute;
top: -6px;
left: 7px;
display: inline-block;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
border-left: 6px solid transparent;
content: '';
}
.colorpicker div {
position: relative;
}
.colorpicker.colorpicker-with-alpha {
min-width: 140px;
}
.colorpicker.colorpicker-with-alpha .colorpicker-alpha {
display: block;
}
.colorpicker-color {
height: 10px;
margin-top: 5px;
clear: both;
background-image: url('../img/bootstrap-colorpicker/alpha.png');
background-position: 0 100%;
}
.colorpicker-color div {
height: 10px;
}
.colorpicker-element .input-group-addon i {
display: block;
width: 16px;
height: 16px;
cursor: pointer;
}
.colorpicker.colorpicker-inline {
position: relative;
display: inline-block;
float: none;
}
.colorpicker.colorpicker-horizontal {
width: 110px;
height: auto;
min-width: 110px;
}
.colorpicker.colorpicker-horizontal .colorpicker-saturation {
margin-bottom: 4px;
}
.colorpicker.colorpicker-horizontal .colorpicker-color {
width: 100px;
}
.colorpicker.colorpicker-horizontal .colorpicker-hue,
.colorpicker.colorpicker-horizontal .colorpicker-alpha {
float: left;
width: 100px;
height: 15px;
margin-bottom: 4px;
margin-left: 0;
cursor: col-resize;
}
.colorpicker.colorpicker-horizontal .colorpicker-hue i,
.colorpicker.colorpicker-horizontal .colorpicker-alpha i {
position: absolute;
top: 0;
left: 0;
display: block;
width: 1px;
height: 15px;
margin-top: 0;
background: #fff;
border: 0;
}
.colorpicker.colorpicker-horizontal .colorpicker-hue {
background-image: url('../img/bootstrap-colorpicker/hue-horizontal.png');
}
.colorpicker.colorpicker-horizontal .colorpicker-alpha {
background-image: url('../img/bootstrap-colorpicker/alpha-horizontal.png');
}
.colorpicker.colorpicker-hidden {
display: none;
}
.colorpicker.colorpicker-visible {
display: block;
}
.colorpicker-inline.colorpicker-visible {
display: inline-block;
}

View File

@ -10,9 +10,9 @@
.btn-info, .btn-info,
.btn-warning, .btn-warning,
.btn-danger { .btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
} }
.btn-default:active, .btn-default:active,
.btn-primary:active, .btn-primary:active,
@ -26,8 +26,8 @@
.btn-info.active, .btn-info.active,
.btn-warning.active, .btn-warning.active,
.btn-danger.active { .btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
} }
.btn:active, .btn:active,
.btn.active { .btn.active {
@ -36,7 +36,7 @@
.btn-default { .btn-default {
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x; background-repeat: repeat-x;
@ -55,7 +55,7 @@
} }
.btn-primary { .btn-primary {
background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x; background-repeat: repeat-x;
@ -73,7 +73,7 @@
} }
.btn-success { .btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x; background-repeat: repeat-x;
@ -91,7 +91,7 @@
} }
.btn-info { .btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x; background-repeat: repeat-x;
@ -109,7 +109,7 @@
} }
.btn-warning { .btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x; background-repeat: repeat-x;
@ -127,7 +127,7 @@
} }
.btn-danger { .btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x; background-repeat: repeat-x;
@ -145,14 +145,14 @@
} }
.thumbnail, .thumbnail,
.img-thumbnail { .img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
} }
.dropdown-menu > li > a:hover, .dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus { .dropdown-menu > li > a:focus {
background-color: #e8e8e8; background-color: #e8e8e8;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
@ -161,50 +161,50 @@
.dropdown-menu > .active > a:focus { .dropdown-menu > .active > a:focus {
background-color: #357ebd; background-color: #357ebd;
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.navbar-default { .navbar-default {
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x; background-repeat: repeat-x;
border-radius: 4px; border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
} }
.navbar-default .navbar-nav > .active > a { .navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
} }
.navbar-brand, .navbar-brand,
.navbar-nav > li > a { .navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, .25); text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
} }
.navbar-inverse { .navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.navbar-inverse .navbar-nav > .active > a { .navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%); background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%);
background-image: linear-gradient(to bottom, #222 0%, #282828 100%); background-image: linear-gradient(to bottom, #222 0%, #282828 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
} }
.navbar-inverse .navbar-brand, .navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a { .navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
} }
.navbar-static-top, .navbar-static-top,
.navbar-fixed-top, .navbar-fixed-top,
@ -212,136 +212,136 @@
border-radius: 0; border-radius: 0;
} }
.alert { .alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2); text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
} }
.alert-success { .alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
border-color: #b2dba1; border-color: #b2dba1;
} }
.alert-info { .alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
border-color: #9acfea; border-color: #9acfea;
} }
.alert-warning { .alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
border-color: #f5e79e; border-color: #f5e79e;
} }
.alert-danger { .alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
border-color: #dca7a7; border-color: #dca7a7;
} }
.progress { .progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.progress-bar { .progress-bar {
background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.progress-bar-success { .progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.progress-bar-info { .progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.progress-bar-warning { .progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.progress-bar-danger { .progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.list-group { .list-group {
border-radius: 4px; border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
} }
.list-group-item.active, .list-group-item.active,
.list-group-item.active:hover, .list-group-item.active:hover,
.list-group-item.active:focus { .list-group-item.active:focus {
text-shadow: 0 -1px 0 #3071a9; text-shadow: 0 -1px 0 #3071a9;
background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
border-color: #3278b3; border-color: #3278b3;
} }
.panel { .panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
} }
.panel-default > .panel-heading { .panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.panel-primary > .panel-heading { .panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.panel-success > .panel-heading { .panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.panel-info > .panel-heading { .panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.panel-warning > .panel-heading { .panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.panel-danger > .panel-heading { .panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
} }
.well { .well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x; background-repeat: repeat-x;
border-color: #dcdcdc; border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
} }
/*# sourceMappingURL=bootstrap-theme.css.map */ /*# sourceMappingURL=bootstrap-theme.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -6,15 +6,15 @@
/* bootstrap modal */ /* bootstrap modal */
.wise-editor .modal { .wise-editor .modal {
overflow: hidden; overflow: hidden;
} }
.modal-backdrop { .modal-backdrop {
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
background: rgba(0,0,0,0.5); background: rgba(0, 0, 0, 0.5);
z-index: 1000; z-index: 1000;
} }

File diff suppressed because one or more lines are too long

View File

@ -1,103 +1,104 @@
div#header { div#header {
width: 100%; width: 100%;
height:50px; height: 50px;
position: absolute; position: absolute;
top: 0; top: 0;
z-index:1000; z-index: 1000;
} }
div#headerNotifier { div#headerNotifier {
border: 1px solid rgb(241, 163, 39); border: 1px solid rgb(241, 163, 39);
background-color: rgb(252, 235, 192); background-color: rgb(252, 235, 192);
border-radius: 3px; border-radius: 3px;
position: fixed; position: fixed;
padding: 5px 9px; padding: 5px 9px;
color: back; color: back;
white-space: nowrap; white-space: nowrap;
margin-top: 5px; margin-top: 5px;
display: none; display: none;
bottom: 10px; bottom: 10px;
} }
div#toolbarRight { div#toolbarRight {
float: right; float: right;
white-space: nowrap; white-space: nowrap;
vertical-align: middle; vertical-align: middle;
justify-content: center; justify-content: center;
margin: 6px 10px; margin: 6px 10px;
height: 100%; height: 100%;
} }
#account { #account {
float: right; float: right;
display: inline; display: inline;
} }
#account >img { #account > img {
width: 36x; width: 36x;
height: 36px; height: 36px;
} }
#accountSettingsPanel{ #accountSettingsPanel {
padding:10px 10px; padding: 10px 10px;
} }
#share { #share {
margin: 0 30px; margin: 0 30px;
float: right; float: right;
} }
.actionButton { .actionButton {
cursor: pointer; cursor: pointer;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
user-select: none; user-select: none;
vertical-align: middle; vertical-align: middle;
justify-content: center; justify-content: center;
padding: 10px 25px; padding: 10px 25px;
font-size: 15px; font-size: 15px;
min-width: 64px; min-width: 64px;
box-sizing: border-box; box-sizing: border-box;
font-weight: 600; font-weight: 600;
border-radius: 9px; border-radius: 9px;
color: white; color: white;
background-color: #ffa800; background-color: #ffa800;
} }
.actionButton:hover { .actionButton:hover {
transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,
box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
} }
div#toolbar { div#toolbar {
width: 100%; width: 100%;
height: 50px; height: 50px;
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
background-color: #fff; background-color: #fff;
min-width: 900px; min-width: 900px;
overflow: hidden; overflow: hidden;
} }
div#toolbar .buttonContainer { div#toolbar .buttonContainer {
height: 50px; height: 50px;
padding-top: 8px; padding-top: 8px;
padding-right: 10px; padding-right: 10px;
padding-left: 10px; padding-left: 10px;
float: left; float: left;
border-left: 1px solid lightgray; border-left: 1px solid lightgray;
} }
div#mapName >span { div#mapName > span {
border-radius: 4px; border-radius: 4px;
float: left; float: left;
padding: 8px; padding: 8px;
min-width: 30px; min-width: 30px;
font-weight: bold; font-weight: bold;
} }
div#backToList { div#backToList {
height: 24px; height: 24px;
width: 24px; width: 24px;
float: left; float: left;
margin: 13px 20px; margin: 13px 20px;
} }
/******************************************************************************************/ /******************************************************************************************/
@ -108,120 +109,120 @@ div#toolbar .buttonOn,
div#toolbar .buttonOff, div#toolbar .buttonOff,
div#toolbar .buttonActive, div#toolbar .buttonActive,
div#toolbar .buttonOn:hover { div#toolbar .buttonOn:hover {
width: 28px; width: 28px;
height: 28px; height: 28px;
float: left; float: left;
text-align: center; text-align: center;
z-index: 4; z-index: 4;
margin-top: 3px; margin-top: 3px;
padding-top: 2px; padding-top: 2px;
padding-left: 2px; padding-left: 2px;
margin-left: 3px; margin-left: 3px;
} }
div#toolbar .buttonOn:hover { div#toolbar .buttonOn:hover {
cursor: pointer; cursor: pointer;
opacity: 1; opacity: 1;
} }
div#toolbar .buttonOn { div#toolbar .buttonOn {
opacity: 0.8; opacity: 0.8;
} }
div#toolbar .buttonOff { div#toolbar .buttonOff {
opacity: 0.4; opacity: 0.4;
} }
div#exportAnchor { div#exportAnchor {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
top: 0; top: 0;
left: 0; left: 0;
} }
div#toolbar .buttonExtOn, div#toolbar .buttonExtOn,
div#toolbar .buttonExtOff, div#toolbar .buttonExtOff,
div#toolbar .buttonExtActive, div#toolbar .buttonExtActive,
div#toolbar .buttonExtOn:hover { div#toolbar .buttonExtOn:hover {
width: 40px; width: 40px;
height: 28px; height: 28px;
float: left; float: left;
text-align: left; text-align: left;
z-index: 4; z-index: 4;
margin-top: 3px; margin-top: 3px;
padding-top: 2px; padding-top: 2px;
padding-left: 5px; padding-left: 5px;
margin-left: 3px; margin-left: 3px;
} }
div#toolbar .buttonExtOn:hover { div#toolbar .buttonExtOn:hover {
opacity: 1; opacity: 1;
} }
div#toolbar .buttonExtActive { div#toolbar .buttonExtActive {
opacity: 1; opacity: 1;
} }
div#toolbar .buttonExtOn { div#toolbar .buttonExtOn {
opacity: 0.8; opacity: 0.8;
cursor: pointer cursor: pointer;
} }
div#toolbar .buttonExtOff { div#toolbar .buttonExtOff {
opacity: 0.4; opacity: 0.4;
} }
div#exportAnchor { div#exportAnchor {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
top: 0; top: 0;
left: 0; left: 0;
} }
/***************************************************************************************************/ /***************************************************************************************************/
/* Other toolbar styles */ /* Other toolbar styles */
/***************************************************************************************************/ /***************************************************************************************************/
.toolbarTip { .toolbarTip {
background-color: #000000; background-color: #000000;
padding: 5px 5px; padding: 5px 5px;
color: #f5f5f5; color: #f5f5f5;
font-size: 11px; font-size: 11px;
} }
div#colorPalette { div#colorPalette {
border: 1px solid #bbb4d6; border: 1px solid #bbb4d6;
display: none; display: none;
position: absolute; position: absolute;
z-index: 4; z-index: 4;
width: 160px; width: 160px;
top: 89px; top: 89px;
} }
div.toolbarPanelLink, div.toolbarPanelLink,
div.toolbarPanelLinkSelectedLink { div.toolbarPanelLinkSelectedLink {
cursor: pointer; cursor: pointer;
color: black; color: black;
margin: 1px; margin: 1px;
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
padding: 5px 10px; padding: 5px 10px;
font-weight: bold; font-weight: bold;
} }
div.toolbarPanelLink:hover, div.toolbarPanelLink:hover,
div.toolbarPanelLinkSelectedLink { div.toolbarPanelLinkSelectedLink {
cursor: pointer; cursor: pointer;
background-color: #efefef; background-color: #efefef;
} }
.toolbarPaneTip { .toolbarPaneTip {
background-color: rgb(228, 226, 210); background-color: rgb(228, 226, 210);
padding: 5px 5px; padding: 5px 5px;
color: #f5f5f5; color: #f5f5f5;
font-size: 11px; font-size: 11px;
border-radius: 6px; border-radius: 6px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2); box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
border: 3px double rgb(190, 190, 190); border: 3px double rgb(190, 190, 190);
} }

View File

@ -34,144 +34,131 @@ import { EditorRenderMode } from '@wisemapping/mindplot';
export type ToolbarActionType = 'export' | 'publish' | 'history' | 'print' | 'share' | 'info'; export type ToolbarActionType = 'export' | 'publish' | 'history' | 'print' | 'share' | 'info';
export type ToolbarPropsType = { export type ToolbarPropsType = {
editorMode: EditorRenderMode; editorMode: EditorRenderMode;
onAction: (action: ToolbarActionType) => void; onAction: (action: ToolbarActionType) => void;
}; };
export default function Toolbar({ export default function Toolbar({
editorMode: editorMode, editorMode: editorMode,
onAction, onAction,
}: ToolbarPropsType): React.ReactElement { }: ToolbarPropsType): React.ReactElement {
const intl = useIntl(); const intl = useIntl();
return ( return (
<HeaderContainer className="wise-editor"> <HeaderContainer className="wise-editor">
<div id="toolbar"> <div id="toolbar">
<div id="backToList"> <div id="backToList">
<img src={BackIconSvg} /> <img src={BackIconSvg} />
</div> </div>
{(editorMode === 'edition-editor' || editorMode === 'edition-owner') && ( {(editorMode === 'edition-editor' || editorMode === 'edition-owner') && (
<div id="persist" className="buttonContainer"> <div id="persist" className="buttonContainer">
<ToolbarButton id="save" className="buttonOn"> <ToolbarButton id="save" className="buttonOn">
<img src={SaveSvg} /> <img src={SaveSvg} />
</ToolbarButton> </ToolbarButton>
</div> </div>
)} )}
{(editorMode === 'edition-editor' || editorMode === 'edition-owner' || editorMode === 'showcase') && ( {(editorMode === 'edition-editor' ||
<> editorMode === 'edition-owner' ||
<div id="edit" className="buttonContainer"> editorMode === 'showcase') && (
<ToolbarButton id="undoEdition" className="buttonOn"> <>
<img src={UndoSvg} /> <div id="edit" className="buttonContainer">
</ToolbarButton> <ToolbarButton id="undoEdition" className="buttonOn">
<ToolbarButton id="redoEdition" className="buttonOn"> <img src={UndoSvg} />
<img src={RedoSvg} /> </ToolbarButton>
</ToolbarButton> <ToolbarButton id="redoEdition" className="buttonOn">
</div> <img src={RedoSvg} />
<div id="nodeStyle" className="buttonContainer"> </ToolbarButton>
<ToolbarButton id="addTopic" className="buttonOn">
<img src={TopicAddSvg} />
</ToolbarButton>
<ToolbarButton id="deleteTopic" className="buttonOn">
<img src={TopicDeleteSvg} />
</ToolbarButton>
<ToolbarButtonExt id="topicBorder" className="buttonExtOn">
<img src={TopicBorderSvg} />
</ToolbarButtonExt>
<ToolbarButtonExt id="topicColor" className="buttonExtOn">
<img src={TopicColorSvg} />
</ToolbarButtonExt>
<ToolbarButtonExt id="topicShape" className="buttonExtOn">
<img src={TopicShapeSvg} />
</ToolbarButtonExt>
</div>
<div id="font" className="buttonContainer">
<ToolbarButton id="fontFamily" className="buttonOn">
<img src={FontTypeSvg} />
</ToolbarButton>
<ToolbarButtonExt id="fontSize" className="buttonExtOn">
<img src={FontSizeSvg} />
</ToolbarButtonExt>
<ToolbarButton id="fontBold" className="buttonOn">
<img src={FontBoldSvg} />
</ToolbarButton>
<ToolbarButton id="fontItalic" className="buttonOn">
<img src={FontItalicSvg} />
</ToolbarButton>
<ToolbarButtonExt id="fontColor" className="buttonExtOn">
<img src={FontColorSvg} />
</ToolbarButtonExt>
</div>
<div id="nodeContent" className="buttonContainer">
<ToolbarButtonExt id="topicIcon" className="buttonExtOn">
<img src={TopicIconSvg} />
</ToolbarButtonExt>
<ToolbarButton id="topicNote" className="buttonOn">
<img src={TopicNoteSvg} />
</ToolbarButton>
<ToolbarButton id="topicLink" className="buttonOn">
<img src={TopicLinkSvg} />
</ToolbarButton>
<ToolbarButton id="topicRelation" className="buttonOn">
<img src={TopicRelationSvg} />
</ToolbarButton>
</div>
<div id="separator" className="buttonContainer"></div>
</>
)}
<ToolbarRightContainer>
<ToolbarButton
id="export"
className="buttonOn"
onClick={() => onAction('export')}
>
<img src={ExportSvg} />
</ToolbarButton>
{(editorMode === 'edition-owner' || editorMode === 'edition-editor' || editorMode === 'edition-viewer') && (
<ToolbarButton
id="print"
className="buttonOn"
onClick={() => onAction('print')}
>
<img src={PrintSvg} />
</ToolbarButton>
)}
<ToolbarButton
id="info"
className="buttonOn"
onClick={() => onAction('info')}
>
<img src={InfoSvg} />
</ToolbarButton>
{editorMode === 'edition-owner' && (
<>
<ToolbarButton
id="history"
className="buttonOn"
onClick={() => onAction('history')}
>
<img src={HistorySvg} />
</ToolbarButton>
<ToolbarButton
id="publishIt"
className="buttonOn"
onClick={() => onAction('publish')}
>
<img src={PublicSvg} />
</ToolbarButton>
</>
)}
{(editorMode === 'edition-owner' || editorMode === 'edition-editor') && (
<ToolbarButton id="account">
<img src={AccountSvg} />
</ToolbarButton>
)}
{editorMode === 'edition-owner' && (
<ActionButton onClick={() => onAction('share')}>
{intl.formatMessage({ id: 'action.share', defaultMessage: 'Share' })}
</ActionButton>
)}
</ToolbarRightContainer>
</div> </div>
</HeaderContainer> <div id="nodeStyle" className="buttonContainer">
); <ToolbarButton id="addTopic" className="buttonOn">
<img src={TopicAddSvg} />
</ToolbarButton>
<ToolbarButton id="deleteTopic" className="buttonOn">
<img src={TopicDeleteSvg} />
</ToolbarButton>
<ToolbarButtonExt id="topicBorder" className="buttonExtOn">
<img src={TopicBorderSvg} />
</ToolbarButtonExt>
<ToolbarButtonExt id="topicColor" className="buttonExtOn">
<img src={TopicColorSvg} />
</ToolbarButtonExt>
<ToolbarButtonExt id="topicShape" className="buttonExtOn">
<img src={TopicShapeSvg} />
</ToolbarButtonExt>
</div>
<div id="font" className="buttonContainer">
<ToolbarButton id="fontFamily" className="buttonOn">
<img src={FontTypeSvg} />
</ToolbarButton>
<ToolbarButtonExt id="fontSize" className="buttonExtOn">
<img src={FontSizeSvg} />
</ToolbarButtonExt>
<ToolbarButton id="fontBold" className="buttonOn">
<img src={FontBoldSvg} />
</ToolbarButton>
<ToolbarButton id="fontItalic" className="buttonOn">
<img src={FontItalicSvg} />
</ToolbarButton>
<ToolbarButtonExt id="fontColor" className="buttonExtOn">
<img src={FontColorSvg} />
</ToolbarButtonExt>
</div>
<div id="nodeContent" className="buttonContainer">
<ToolbarButtonExt id="topicIcon" className="buttonExtOn">
<img src={TopicIconSvg} />
</ToolbarButtonExt>
<ToolbarButton id="topicNote" className="buttonOn">
<img src={TopicNoteSvg} />
</ToolbarButton>
<ToolbarButton id="topicLink" className="buttonOn">
<img src={TopicLinkSvg} />
</ToolbarButton>
<ToolbarButton id="topicRelation" className="buttonOn">
<img src={TopicRelationSvg} />
</ToolbarButton>
</div>
<div id="separator" className="buttonContainer"></div>
</>
)}
<ToolbarRightContainer>
<ToolbarButton id="export" className="buttonOn" onClick={() => onAction('export')}>
<img src={ExportSvg} />
</ToolbarButton>
{(editorMode === 'edition-owner' ||
editorMode === 'edition-editor' ||
editorMode === 'edition-viewer') && (
<ToolbarButton id="print" className="buttonOn" onClick={() => onAction('print')}>
<img src={PrintSvg} />
</ToolbarButton>
)}
<ToolbarButton id="info" className="buttonOn" onClick={() => onAction('info')}>
<img src={InfoSvg} />
</ToolbarButton>
{editorMode === 'edition-owner' && (
<>
<ToolbarButton id="history" className="buttonOn" onClick={() => onAction('history')}>
<img src={HistorySvg} />
</ToolbarButton>
<ToolbarButton
id="publishIt"
className="buttonOn"
onClick={() => onAction('publish')}
>
<img src={PublicSvg} />
</ToolbarButton>
</>
)}
{(editorMode === 'edition-owner' || editorMode === 'edition-editor') && (
<ToolbarButton id="account">
<img src={AccountSvg} />
</ToolbarButton>
)}
{editorMode === 'edition-owner' && (
<ActionButton onClick={() => onAction('share')}>
{intl.formatMessage({ id: 'action.share', defaultMessage: 'Share' })}
</ActionButton>
)}
</ToolbarRightContainer>
</div>
</HeaderContainer>
);
} }

View File

@ -1,282 +1,283 @@
/********************************************************************************/ /********************************************************************************/
/* Header & Toolbar Styles */ /* Header & Toolbar Styles */
/********************************************************************************/ /********************************************************************************/
@import "bootstrap-prefix.min.css"; @import 'bootstrap-prefix.min.css';
@import "bootstrap-fixes.css"; @import 'bootstrap-fixes.css';
html { html {
/* avoid bootstrap overriding font-size and breaking Mui */ /* avoid bootstrap overriding font-size and breaking Mui */
font-size: initial; font-size: initial;
} }
body { body {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
min-width: 100vw; min-width: 100vw;
min-height: 100vh; min-height: 100vh;
margin: 0px; margin: 0px;
} }
.mindplot-root { .mindplot-root {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
div#mindplot { div#mindplot {
position: relative; position: relative;
top: 50px; top: 50px;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
border: 0; border: 0;
overflow: hidden; overflow: hidden;
opacity: 1; opacity: 1;
background-color: #f2f2f2; background-color: #f2f2f2;
background-image: linear-gradient(#ebe9e7 1px, transparent 1px), linear-gradient(to right, #ebe9e7 1px, #f2f2f2 1px); background-image: linear-gradient(#ebe9e7 1px, transparent 1px),
background-size: 50px 50px; linear-gradient(to right, #ebe9e7 1px, #f2f2f2 1px);
background-size: 50px 50px;
} }
.notesTip { .notesTip {
background-color: #dfcf3c; background-color: #dfcf3c;
padding: 5px 15px; padding: 5px 15px;
color: #666666; color: #666666;
/*font-weight: bold;*/ /*font-weight: bold;*/
/*width: 100px;*/ /*width: 100px;*/
font-size: 13px; font-size: 13px;
-moz-border-radius: 3px; -moz-border-radius: 3px;
-webkit-border-radius: 3px; -webkit-border-radius: 3px;
border-radius: 3px; border-radius: 3px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2); box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
} }
.linkTip { .linkTip {
background-color: #dfcf3c; background-color: #dfcf3c;
padding: 5px 15px; padding: 5px 15px;
color: #666666; color: #666666;
/*font-weight: bold;*/ /*font-weight: bold;*/
/*width: 100px;*/ /*width: 100px;*/
font-size: 13px; font-size: 13px;
-moz-border-radius: 3px; -moz-border-radius: 3px;
-webkit-border-radius: 3px; -webkit-border-radius: 3px;
border-radius: 3px; border-radius: 3px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2); box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
} }
.keyboardShortcutTip { .keyboardShortcutTip {
background-color: black; background-color: black;
padding: 5px 15px; padding: 5px 15px;
color: white; color: white;
font-weight: bold; font-weight: bold;
font-size: 11px; font-size: 11px;
} }
/** */ /** */
/* Modal dialogs definitions */ /* Modal dialogs definitions */
div.modalDialog { div.modalDialog {
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;
z-index: 11000; z-index: 11000;
width: 500px; width: 500px;
margin: -250px 0 0 -250px; margin: -250px 0 0 -250px;
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #999; border: 1px solid #999;
padding: 10px; padding: 10px;
overflow: auto; overflow: auto;
-webkit-background-clip: padding-box; -webkit-background-clip: padding-box;
-moz-background-clip: padding-box; -moz-background-clip: padding-box;
background-clip: padding-box; background-clip: padding-box;
} }
div.modalDialog .content { div.modalDialog .content {
padding: 5px 5px; padding: 5px 5px;
} }
div.modalDialog .title { div.modalDialog .title {
font-weight: bold; font-weight: bold;
text-shadow: 1px 1px 0 #fff; text-shadow: 1px 1px 0 #fff;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
padding: 5px 15px; padding: 5px 15px;
font-size: 18px; font-size: 18px;
} }
/*--- End Modal Dialog Form ---*/ /*--- End Modal Dialog Form ---*/
.publishModalDialog .content { .publishModalDialog .content {
height: 420px; height: 420px;
} }
.exportModalDialog .content { .exportModalDialog .content {
height: 400px; height: 400px;
} }
.shareModalDialog .content { .shareModalDialog .content {
height: 440px; height: 440px;
} }
div.shareModalDialog { div.shareModalDialog {
width: 550px; width: 550px;
} }
.panelIcon { .panelIcon {
width: 20px; width: 20px;
height: 20px; height: 20px;
margin-left: 4px; margin-left: 4px;
margin-top: 3px; margin-top: 3px;
cursor: pointer cursor: pointer;
} }
.panelIcon:hover { .panelIcon:hover {
background-color: #efefef; background-color: #efefef;
} }
.wise-editor .popover { .wise-editor .popover {
font-size: 13px; font-size: 13px;
max-width: none; max-width: none;
} }
#floating-panel { #floating-panel {
position: fixed; position: fixed;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
bottom: 20px; bottom: 20px;
right: 20px; right: 20px;
align-items: stretch; align-items: stretch;
} }
div#position { div#position {
margin-top: 5px; margin-top: 5px;
} }
#position-button { #position-button {
cursor: pointer; cursor: pointer;
border: solid black 1px; border: solid black 1px;
width: 40px; width: 40px;
height: 40px; height: 40px;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 40px 40px; background-size: 40px 40px;
background-color: #FFF; background-color: #fff;
border-radius: 8px; border-radius: 8px;
} }
#zoom-button { #zoom-button {
width: 40px; width: 40px;
border: 0; border: 0;
} }
#zoom-plus, #zoom-plus,
#zoom-minus { #zoom-minus {
border: solid black 1px; border: solid black 1px;
height: 40px; height: 40px;
width: 40px; width: 40px;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 40px 40px; background-size: 40px 40px;
background-position: center; background-position: center;
cursor: pointer; cursor: pointer;
background-color: #FFF; background-color: #fff;
} }
#zoom-plus { #zoom-plus {
border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0;
} }
#zoom-minus { #zoom-minus {
border-radius: 0 0 8px 8px; border-radius: 0 0 8px 8px;
} }
div#shotcuts > img{ div#shotcuts > img {
margin: 20px 0; margin: 20px 0;
width: 40px; width: 40px;
height: 40px; height: 40px;
} }
#keyboardTable { #keyboardTable {
font-family: Arial, verdana, serif; font-family: Arial, verdana, serif;
font-size: 13px; font-size: 13px;
width: 100%; width: 100%;
} }
#keyboardTable td { #keyboardTable td {
padding: 3px; padding: 3px;
white-space: nowrap; white-space: nowrap;
} }
#keyboardTable th { #keyboardTable th {
padding: 5px; padding: 5px;
white-space: nowrap; white-space: nowrap;
} }
#keyboardTable th { #keyboardTable th {
background-color: #000000; background-color: #000000;
color: #ffffff; color: #ffffff;
} }
.tryInfoPanel { .tryInfoPanel {
position: absolute; position: absolute;
text-align: center; text-align: center;
left: 0; left: 0;
right: 0; right: 0;
background-color: white; background-color: white;
border: solid 2px #ffa800; border: solid 2px #ffa800;
margin: auto; margin: auto;
width: 99%; width: 99%;
border-radius: 9px; border-radius: 9px;
width: 96%; width: 96%;
} }
@media (min-width: 600px) { @media (min-width: 600px) {
.tryInfoPanel { .tryInfoPanel {
font-size: 15px; font-size: 15px;
} }
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.tryInfoPanel { .tryInfoPanel {
font-size: 13px; font-size: 13px;
} }
} }
.tryInfoPanel .tryInfoPanelInner { .tryInfoPanel .tryInfoPanelInner {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
} }
.tryInfoPanel .tryInfoPanelInner .closeButton { .tryInfoPanel .tryInfoPanelInner .closeButton {
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 5px; right: 5px;
} }
.tryInfoPanel .tryInfoPanelInner .closeButton button { .tryInfoPanel .tryInfoPanelInner .closeButton button {
cursor: pointer; cursor: pointer;
border-style: hidden; border-style: hidden;
background-color: transparent; background-color: transparent;
padding: 0px; padding: 0px;
} }
.tryInfoPanel .tryInfoPanelInner .closeButton button img { .tryInfoPanel .tryInfoPanelInner .closeButton button img {
width: 18px; width: 18px;
height: 18px; height: 18px;
filter: invert(73%) sepia(21%) saturate(4699%) hue-rotate(357deg) brightness(98%) contrast(108%); filter: invert(73%) sepia(21%) saturate(4699%) hue-rotate(357deg) brightness(98%) contrast(108%);
} }
.tryInfoPanelWithToolbar { .tryInfoPanelWithToolbar {
top: 55px; top: 55px;
} }
.tryInfoPanelWithoutToolbar { .tryInfoPanelWithoutToolbar {
top: 5px; top: 5px;
} }
.tryInfoPanelClosed { .tryInfoPanelClosed {
display: none; display: none;
} }
.tryInfoPanel > p { .tryInfoPanel > p {
justify-content: center; justify-content: center;
} }

View File

@ -1,33 +1,33 @@
/* Overwrite some styles */ /* Overwrite some styles */
div#footer { div#footer {
width: 100%; width: 100%;
padding: 20px 30px; padding: 20px 30px;
height: 80px; height: 80px;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
background-color: #ffa800; background-color: #ffa800;
} }
div#footer-desc { div#footer-desc {
float: left; float: left;
height: 100px; height: 100px;
padding: 0px 10px; padding: 0px 10px;
} }
div#footer-logo { div#footer-logo {
float: left; float: left;
height: 100px; height: 100px;
} }
#floating-panel { #floating-panel {
bottom: 20px; bottom: 20px;
align-items: stretch; align-items: stretch;
} }
div#mindplot { div#mindplot {
top:0; top: 0;
} }
#toolbar { #toolbar {
display: none; display: none;
} }

View File

@ -21,7 +21,6 @@ import Editor, { EditorOptions } from '../../../../src/index';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
const initialization = (designer: Designer) => { const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => { designer.addEvent('loadSuccess', () => {
const elem = document.getElementById('mindplot'); const elem = document.getElementById('mindplot');
if (elem) { if (elem) {
@ -35,10 +34,10 @@ const mapId = 'welcome';
const options: EditorOptions = { const options: EditorOptions = {
zoom: 0.8, zoom: 0.8,
locked: false, locked: false,
mapTitle: "Develop WiseMapping", mapTitle: 'Develop WiseMapping',
mode: 'edition-owner', mode: 'edition-owner',
locale: 'en', locale: 'en',
enableKeyboardEvents: true enableKeyboardEvents: true,
}; };
ReactDOM.render( ReactDOM.render(

View File

@ -5,7 +5,6 @@ import Editor, { EditorOptions } from '../../../../src/index';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
const initialization = (designer: Designer) => { const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => { designer.addEvent('loadSuccess', () => {
const elem = document.getElementById('mindplot'); const elem = document.getElementById('mindplot');
if (elem) { if (elem) {
@ -25,7 +24,6 @@ const initialization = (designer: Designer) => {
option.selected = option.value === mapId; option.selected = option.value === mapId;
}); });
} }
}); });
}; };
@ -36,10 +34,10 @@ const persistence = new LocalStorageManager('samples/{id}.wxml', false);
const options: EditorOptions = { const options: EditorOptions = {
zoom: 0.8, zoom: 0.8,
locked: false, locked: false,
mapTitle: "Develop WiseMapping", mapTitle: 'Develop WiseMapping',
mode: 'viewonly', mode: 'viewonly',
locale: 'en', locale: 'en',
enableKeyboardEvents: true enableKeyboardEvents: true,
}; };
ReactDOM.render( ReactDOM.render(

View File

@ -1,25 +1,24 @@
body body {
{ font-size: 1em !important;
font-size: 1em !important; color: #000 !important;
color: #000 !important; font-family: Arial !important;
font-family: Arial !important;
} }
table { table {
border: 1px solid darkgray; border: 1px solid darkgray;
border-spacing: 0px; border-spacing: 0px;
} }
td { td {
border: 1px solid darkgray; border: 1px solid darkgray;
padding: 10px; padding: 10px;
} }
tbody tr td:first-child { tbody tr td:first-child {
width: 20%; width: 20%;
} }
.eventForm { .eventForm {
float: left; float: left;
margin: 10px; margin: 10px;
} }

View File

@ -11,45 +11,42 @@ import Button from '@mui/material/Button';
import AlertTitle from '@mui/material/AlertTitle'; import AlertTitle from '@mui/material/AlertTitle';
const ClientHealthSentinel = (): React.ReactElement => { const ClientHealthSentinel = (): React.ReactElement => {
const status: ClientStatus = useSelector(activeInstanceStatus); const status: ClientStatus = useSelector(activeInstanceStatus);
const handleOnClose = () => { const handleOnClose = () => {
window.location.href = '/c/login'; window.location.href = '/c/login';
}; };
return ( return (
<div> <div>
<Dialog <Dialog
open={status.state != 'healthy'} open={status.state != 'healthy'}
onClose={handleOnClose} onClose={handleOnClose}
maxWidth="sm" maxWidth="sm"
fullWidth={true} fullWidth={true}
> >
<DialogTitle> <DialogTitle>
<FormattedMessage <FormattedMessage id="expired.title" defaultMessage="Your session has expired" />
id="expired.title" </DialogTitle>
defaultMessage="Your session has expired"
/>
</DialogTitle>
<DialogContent> <DialogContent>
<Alert severity="error"> <Alert severity="error">
<AlertTitle> <AlertTitle>
<FormattedMessage <FormattedMessage
id="expired.description" id="expired.description"
defaultMessage="Your current session has expired. Please, sign in and try again." defaultMessage="Your current session has expired. Please, sign in and try again."
/> />
</AlertTitle> </AlertTitle>
</Alert> </Alert>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button type="button" color="primary" size="medium" onClick={handleOnClose}> <Button type="button" color="primary" size="medium" onClick={handleOnClose}>
<FormattedMessage id="login.signin" defaultMessage="Sign In" /> <FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</div> </div>
); );
}; };
export default ClientHealthSentinel; export default ClientHealthSentinel;

View File

@ -13,78 +13,82 @@ import { activeInstance, fetchAccount, fetchMapById } from '../../redux/clientSl
import EditorOptionsBuilder from './EditorOptionsBuilder'; import EditorOptionsBuilder from './EditorOptionsBuilder';
export type EditorPropsType = { export type EditorPropsType = {
isTryMode: boolean; isTryMode: boolean;
}; };
const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => { const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => {
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null); const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
const hotkey = useSelector(hotkeysEnabled); const hotkey = useSelector(hotkeysEnabled);
const userLocale = AppI18n.getUserLocale(); const userLocale = AppI18n.getUserLocale();
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: `Map Editor` }); ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: `Map Editor` });
}, []); }, []);
const findEditorMode = (isTryMode: boolean, mapId: number): EditorRenderMode | null => { const findEditorMode = (isTryMode: boolean, mapId: number): EditorRenderMode | null => {
let result: EditorRenderMode = null; let result: EditorRenderMode = null;
if (isTryMode) { if (isTryMode) {
result = 'showcase'; result = 'showcase';
} else if (global.mindmapLocked) { } else if (global.mindmapLocked) {
result = 'viewonly'; result = 'viewonly';
} else { } else {
const fetchResult = fetchMapById(mapId); const fetchResult = fetchMapById(mapId);
if (!fetchResult.isLoading) { if (!fetchResult.isLoading) {
if (fetchResult.error) { if (fetchResult.error) {
throw new Error(`Map info could not be loaded: ${JSON.stringify(fetchResult.error)}`); throw new Error(`Map info could not be loaded: ${JSON.stringify(fetchResult.error)}`);
}
if (!fetchResult.map) {
throw new Error(`Map info could not be loaded. Info not present: ${JSON.stringify(fetchResult)}`);
}
result = `edition-${fetchResult.map.role}`;
}
} }
return result;
if (!fetchResult.map) {
throw new Error(
`Map info could not be loaded. Info not present: ${JSON.stringify(fetchResult)}`,
);
}
result = `edition-${fetchResult.map.role}`;
}
} }
return result;
};
// What is the role ? // What is the role ?
const mapId = EditorOptionsBuilder.loadMapId(); const mapId = EditorOptionsBuilder.loadMapId();
const mode = findEditorMode(isTryMode, mapId); const mode = findEditorMode(isTryMode, mapId);
// Account settings can be null and editor cannot be initilized multiple times. This creates problems // Account settings can be null and editor cannot be initilized multiple times. This creates problems
// at the i18n resource loading. // at the i18n resource loading.
const isAccountLoaded = mode === 'showcase' || fetchAccount; const isAccountLoaded = mode === 'showcase' || fetchAccount;
const loadCompleted = mode && isAccountLoaded; const loadCompleted = mode && isAccountLoaded;
let options, persistence: PersistenceManager; let options, persistence: PersistenceManager;
if (loadCompleted) { if (loadCompleted) {
options = EditorOptionsBuilder.build(userLocale.code, mode, hotkey); options = EditorOptionsBuilder.build(userLocale.code, mode, hotkey);
persistence = client.buildPersistenceManager(mode); persistence = client.buildPersistenceManager(mode);
} }
return loadCompleted ? (
<IntlProvider
locale={userLocale.code}
defaultLocale={Locales.EN.code}
messages={userLocale.message as Record<string, string>}
>
<Editor onAction={setActiveDialog}
options={options}
persistenceManager={persistence}
mapId={mapId} />
{
activeDialog &&
<ActionDispatcher
action={activeDialog}
onClose={() => setActiveDialog(null)}
mapsId={[mapId]}
fromEditor
/>
}
</IntlProvider>) : <></>
}
return loadCompleted ? (
<IntlProvider
locale={userLocale.code}
defaultLocale={Locales.EN.code}
messages={userLocale.message as Record<string, string>}
>
<Editor
onAction={setActiveDialog}
options={options}
persistenceManager={persistence}
mapId={mapId}
/>
{activeDialog && (
<ActionDispatcher
action={activeDialog}
onClose={() => setActiveDialog(null)}
mapsId={[mapId]}
fromEditor
/>
)}
</IntlProvider>
) : (
<></>
);
};
export default EditorPage; export default EditorPage;

View File

@ -18,78 +18,85 @@ import Typography from '@mui/material/Typography';
import { getCsrfToken, getCsrfTokenParameter } from '../../utils'; import { getCsrfToken, getCsrfTokenParameter } from '../../utils';
const ForgotPassword = () => { const ForgotPassword = () => {
const [email, setEmail] = useState<string>(''); const [email, setEmail] = useState<string>('');
const [error, setError] = useState<ErrorInfo>(); const [error, setError] = useState<ErrorInfo>();
const history = useHistory(); const history = useHistory();
const intl = useIntl(); const intl = useIntl();
const service: Client = useSelector(activeInstance); const service: Client = useSelector(activeInstance);
const mutation = useMutation<void, ErrorInfo, string>( const mutation = useMutation<void, ErrorInfo, string>(
(email: string) => service.resetPassword(email), (email: string) => service.resetPassword(email),
{ {
onSuccess: () => history.push('/c/forgot-password-success'), onSuccess: () => history.push('/c/forgot-password-success'),
onError: (error) => { onError: (error) => {
setError(error); setError(error);
}, },
} },
); );
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
mutation.mutate(email); mutation.mutate(email);
}; };
return ( return (
<FormContainer> <FormContainer>
<Typography variant="h4" component="h1"> <Typography variant="h4" component="h1">
<FormattedMessage id="forgot.title" defaultMessage="Reset your password" /> <FormattedMessage id="forgot.title" defaultMessage="Reset your password" />
</Typography> </Typography>
<Typography> <Typography>
<FormattedMessage <FormattedMessage
id="forgot.desc" id="forgot.desc"
defaultMessage="We will send you an email to reset your password." defaultMessage="We will send you an email to reset your password."
/> />
</Typography> </Typography>
<GlobalError error={error} /> <GlobalError error={error} />
<form onSubmit={handleOnSubmit}> <form onSubmit={handleOnSubmit}>
<input type='hidden' value={getCsrfToken()} name={getCsrfTokenParameter()} /> <input type="hidden" value={getCsrfToken()} name={getCsrfTokenParameter()} />
<Input <Input
type="email" type="email"
name="email" name="email"
label={intl.formatMessage({ id: 'forgot.email', defaultMessage: 'Email' })} label={intl.formatMessage({ id: 'forgot.email', defaultMessage: 'Email' })}
autoComplete="email" autoComplete="email"
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
error={error} error={error}
/> />
<SubmitButton <SubmitButton
value={intl.formatMessage({ value={intl.formatMessage({
id: 'forgot.register', id: 'forgot.register',
defaultMessage: 'Send recovery link', defaultMessage: 'Send recovery link',
})} })}
/> />
</form> </form>
</FormContainer> </FormContainer>
); );
}; };
const ForgotPasswordPage = (): React.ReactElement => { const ForgotPasswordPage = (): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
useEffect(() => { useEffect(() => {
document.title = intl.formatMessage({ id: 'forgot.page-title', defaultMessage: 'Forgot Password | WiseMapping' }); document.title = intl.formatMessage({
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'ForgotPassword:Init' }); id: 'forgot.page-title',
}, []); defaultMessage: 'Forgot Password | WiseMapping',
});
ReactGA.send({
hitType: 'pageview',
page: window.location.pathname,
title: 'ForgotPassword:Init',
});
}, []);
return ( return (
<div> <div>
<Header type="only-signin" /> <Header type="only-signin" />
<ForgotPassword /> <ForgotPassword />
<Footer /> <Footer />
</div> </div>
); );
}; };
export { ForgotPasswordPage }; export { ForgotPasswordPage };

View File

@ -9,45 +9,52 @@ import Button from '@mui/material/Button';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
const ForgotPasswordSuccessPage = (): React.ReactElement => { const ForgotPasswordSuccessPage = (): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
useEffect(() => { useEffect(() => {
document.title = intl.formatMessage({ id: 'forgotsuccess.page-title', defaultMessage: 'Password Recovered | WiseMapping' }); document.title = intl.formatMessage({
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'ForgotPassword:Success' }); id: 'forgotsuccess.page-title',
defaultMessage: 'Password Recovered | WiseMapping',
}); });
ReactGA.send({
hitType: 'pageview',
page: window.location.pathname,
title: 'ForgotPassword:Success',
});
});
return ( return (
<div> <div>
<Header type="none" /> <Header type="none" />
<FormContainer> <FormContainer>
<Typography variant="h4" component="h1"> <Typography variant="h4" component="h1">
<FormattedMessage <FormattedMessage
id="forgot.success.title" id="forgot.success.title"
defaultMessage="Your temporal password has been sent." defaultMessage="Your temporal password has been sent."
/> />
</Typography> </Typography>
<Typography paragraph> <Typography paragraph>
<FormattedMessage <FormattedMessage
id="forgot.success.desc" id="forgot.success.desc"
defaultMessage="We've sent you an email that will allow you to reset your password. You should receive it in the next minutes." defaultMessage="We've sent you an email that will allow you to reset your password. You should receive it in the next minutes."
/> />
</Typography> </Typography>
<Button <Button
color="primary" color="primary"
size="medium" size="medium"
variant="contained" variant="contained"
component={RouterLink} component={RouterLink}
to="/c/login" to="/c/login"
disableElevation={true} disableElevation={true}
> >
<FormattedMessage id="login.signin" defaultMessage="Sign In" /> <FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button> </Button>
</FormContainer> </FormContainer>
<Footer /> <Footer />
</div> </div>
); );
}; };
export default ForgotPasswordSuccessPage; export default ForgotPasswordSuccessPage;

View File

@ -3,19 +3,19 @@ import { ErrorInfo } from '../../../classes/client';
import StyledAlert from './styled'; import StyledAlert from './styled';
type GlobalErrorProps = { type GlobalErrorProps = {
error?: ErrorInfo; error?: ErrorInfo;
}; };
const GlobalError = (props: GlobalErrorProps): React.ReactElement | null => { const GlobalError = (props: GlobalErrorProps): React.ReactElement | null => {
const error = props.error; const error = props.error;
const hasError = Boolean(error?.msg); const hasError = Boolean(error?.msg);
const errorMsg = error?.msg; const errorMsg = error?.msg;
return hasError ? ( return hasError ? (
<StyledAlert severity="error" variant="filled" hidden={!hasError}> <StyledAlert severity="error" variant="filled" hidden={!hasError}>
{errorMsg} {errorMsg}
</StyledAlert> </StyledAlert>
) : null; ) : null;
}; };
export default GlobalError; export default GlobalError;

View File

@ -3,51 +3,51 @@ import React, { ChangeEvent } from 'react';
import { ErrorInfo } from '../../../classes/client'; import { ErrorInfo } from '../../../classes/client';
type InputProps = { type InputProps = {
name: string; name: string;
error?: ErrorInfo; error?: ErrorInfo;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void; onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
label: string; label: string;
required?: boolean; required?: boolean;
type: string; type: string;
value?: string; value?: string;
autoComplete?: string; autoComplete?: string;
fullWidth?: boolean; fullWidth?: boolean;
disabled?: boolean; disabled?: boolean;
maxLength?: number, maxLength?: number;
rows?: number rows?: number;
}; };
const Input = ({ const Input = ({
name, name,
error, error,
onChange, onChange,
required = true, required = true,
type, type,
value, value,
label, label,
autoComplete, autoComplete,
fullWidth = true, fullWidth = true,
disabled = false, disabled = false,
maxLength = 254, maxLength = 254,
}: InputProps): React.ReactElement => { }: InputProps): React.ReactElement => {
const fieldError = error?.fields?.[name]; const fieldError = error?.fields?.[name];
return ( return (
<TextField <TextField
name={name} name={name}
type={type} type={type}
label={label} label={label}
value={value} value={value}
onChange={onChange} onChange={onChange}
error={Boolean(fieldError)} error={Boolean(fieldError)}
helperText={fieldError} helperText={fieldError}
variant="outlined" variant="outlined"
required={required} required={required}
fullWidth={fullWidth} fullWidth={fullWidth}
margin="dense" margin="dense"
disabled={disabled} disabled={disabled}
autoComplete={autoComplete} autoComplete={autoComplete}
inputProps={{ maxLength: maxLength }} inputProps={{ maxLength: maxLength }}
/> />
); );
}; };
export default Input; export default Input;

View File

@ -3,37 +3,37 @@ import React, { useState } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
type SubmitButton = { type SubmitButton = {
value: string; value: string;
disabled?: boolean; disabled?: boolean;
}; };
const SubmitButton = (props: SubmitButton): React.ReactElement => { const SubmitButton = (props: SubmitButton): React.ReactElement => {
const [disabled] = useState(props.disabled ? true : false); const [disabled] = useState(props.disabled ? true : false);
const intl = useIntl(); const intl = useIntl();
let valueTxt = props.value; let valueTxt = props.value;
if (disabled) { if (disabled) {
valueTxt = intl.formatMessage({ id: 'common.wait', defaultMessage: 'Please wait ...' }); valueTxt = intl.formatMessage({ id: 'common.wait', defaultMessage: 'Please wait ...' });
} }
const [value] = useState(valueTxt); const [value] = useState(valueTxt);
return ( return (
<Button <Button
color="primary" color="primary"
size="medium" size="medium"
variant="contained" variant="contained"
type="submit" type="submit"
disableElevation={true} disableElevation={true}
disabled={disabled} disabled={disabled}
style={{ style={{
width: '350px', width: '350px',
height: '53px', height: '53px',
padding: '0px 20px', padding: '0px 20px',
margin: '7px 0px', margin: '7px 0px',
fontSize: '18px', fontSize: '18px',
}} }}
> >
{value} {value}
</Button> </Button>
); );
}; };
export default SubmitButton; export default SubmitButton;

View File

@ -5,79 +5,70 @@ import poweredByIcon from './pwrdby-white.svg';
// eslint-disable-next-line // eslint-disable-next-line
const Footer = (): React.ReactElement => { const Footer = (): React.ReactElement => {
return ( return (
<StyledFooter> <StyledFooter>
<div style={{ padding: 0, margin: 0 }}> <div style={{ padding: 0, margin: 0 }}>
<a href="http://www.wisemapping.org/"> <a href="http://www.wisemapping.org/">
<img src={poweredByIcon} alt="Powered By WiseMapping" /> <img src={poweredByIcon} alt="Powered By WiseMapping" />
</a> </a>
</div> </div>
<div> <div>
<h4> <h4>
<FormattedMessage id="footer.faqandhelp" defaultMessage="Help & FAQ" /> <FormattedMessage id="footer.faqandhelp" defaultMessage="Help & FAQ" />
</h4> </h4>
<div> <div>
<a href="https://www.wisemapping.com/aboutus.html"> <a href="https://www.wisemapping.com/aboutus.html">
<FormattedMessage id="footer.aboutus" defaultMessage="About Us" /> <FormattedMessage id="footer.aboutus" defaultMessage="About Us" />
</a> </a>
</div> </div>
<div> <div>
<a href="https://www.wisemapping.com/faq.html"> <a href="https://www.wisemapping.com/faq.html">
<FormattedMessage id="footer.faq" defaultMessage="F.A.Q." /> <FormattedMessage id="footer.faq" defaultMessage="F.A.Q." />
</a> </a>
</div> </div>
<div> <div>
<a href="https://www.wisemapping.com/termsofuse.html"> <a href="https://www.wisemapping.com/termsofuse.html">
<FormattedMessage <FormattedMessage id="footer.termsandconditions" defaultMessage="Term And Conditions" />
id="footer.termsandconditions" </a>
defaultMessage="Term And Conditions" </div>
/> </div>
</a> <div>
</div> <h4>
</div> <FormattedMessage id="footer.contactus" defaultMessage="Contact Us" />
<div> </h4>
<h4> <div>
<FormattedMessage id="footer.contactus" defaultMessage="Contact Us" /> <a href="mailto:support@wisemapping.com">
</h4> <FormattedMessage id="footer.support" defaultMessage="Support" />
<div> </a>
<a href="mailto:support@wisemapping.com"> </div>
<FormattedMessage id="footer.support" defaultMessage="Support" /> <div>
</a> <a href="mailto:feedback@wisemapping.com">
</div> <FormattedMessage id="footer.feedback" defaultMessage="Feedback" />
<div> </a>
<a href="mailto:feedback@wisemapping.com"> </div>
<FormattedMessage id="footer.feedback" defaultMessage="Feedback" /> <div>
</a> <a href="mailto:team@wisemapping.com">
</div> <FormattedMessage id="footer.team" defaultMessage="Our Team" />
<div> </a>
<a href="mailto:team@wisemapping.com"> </div>
<FormattedMessage id="footer.team" defaultMessage="Our Team" /> </div>
</a> <div>
</div> <h4>
</div> <FormattedMessage id="footer.others" defaultMessage="Others" />
<div> </h4>
<h4> <div>
<FormattedMessage id="footer.others" defaultMessage="Others" /> <a href="https://www.paypal.com/donate/?hosted_button_id=CF7GJ7T6E4RS4">
</h4> <FormattedMessage id="footer.donations" defaultMessage="Donations" />
<div> </a>
<a href="https://www.paypal.com/donate/?hosted_button_id=CF7GJ7T6E4RS4"> </div>
<FormattedMessage <div>
id="footer.donations" <a href="http://www.wisemapping.org/">
defaultMessage="Donations" <FormattedMessage id="footer.opensource" defaultMessage="Open Source" />
/> </a>
</a> </div>
</div> </div>
<div> </StyledFooter>
<a href="http://www.wisemapping.org/"> );
<FormattedMessage
id="footer.opensource"
defaultMessage="Open Source"
/>
</a>
</div>
</div>
</StyledFooter>
);
}; };
export default Footer; export default Footer;

View File

@ -2,11 +2,11 @@ import Container from '@mui/material/Container';
import withStyles from '@mui/styles/withStyles'; import withStyles from '@mui/styles/withStyles';
const FormContainer = withStyles({ const FormContainer = withStyles({
root: { root: {
padding: '20px 10px 0px 20px', padding: '20px 10px 0px 20px',
maxWidth: '380px', maxWidth: '380px',
textAlign: 'center', textAlign: 'center',
}, },
})(Container); })(Container);
export default FormContainer; export default FormContainer;

View File

@ -8,89 +8,83 @@ import Button from '@mui/material/Button';
import logo from './logo-small.svg'; import logo from './logo-small.svg';
interface HeaderProps { interface HeaderProps {
type: 'only-signup' | 'only-signin' | 'none'; type: 'only-signup' | 'only-signin' | 'none';
} }
export const Header = ({ type }: HeaderProps): React.ReactElement => { export const Header = ({ type }: HeaderProps): React.ReactElement => {
let signUpButton; let signUpButton;
let text; let text;
let signInButton; let signInButton;
if (type === 'only-signup') { if (type === 'only-signup') {
text = ( text = (
<span className="header-area-content-span"> <span className="header-area-content-span">
<span> <span>
<FormattedMessage <FormattedMessage id="header.donthaveaccount" defaultMessage="Don't have an account ?" />
id="header.donthaveaccount" </span>
defaultMessage="Don't have an account ?" </span>
/>
</span>
</span>
);
signUpButton = <SignUpButton className="header-area-right2" />;
} else if (type === 'only-signin') {
text = (
<span className="header-area-content-span">
<span>
<FormattedMessage
id="header.haveaccount"
defaultMessage="Already have an account?"
/>
</span>
</span>
);
signUpButton = <SignInButton className="header-area-right2" />;
} else if (type === 'none') {
text = '';
signUpButton = '';
} else {
signUpButton = <SignUpButton className="header-area-right2" />;
signInButton = <SignInButton className="header-area-right2" />;
}
return (
<StyledNav>
<StyledDiv>
<Logo>
<Link to="/c/login" className="header-logo">
<img src={String(logo)} alt="logo" />
</Link>
</Logo>
{text}
{signUpButton}
{signInButton}
</StyledDiv>
</StyledNav>
); );
signUpButton = <SignUpButton className="header-area-right2" />;
} else if (type === 'only-signin') {
text = (
<span className="header-area-content-span">
<span>
<FormattedMessage id="header.haveaccount" defaultMessage="Already have an account?" />
</span>
</span>
);
signUpButton = <SignInButton className="header-area-right2" />;
} else if (type === 'none') {
text = '';
signUpButton = '';
} else {
signUpButton = <SignUpButton className="header-area-right2" />;
signInButton = <SignInButton className="header-area-right2" />;
}
return (
<StyledNav>
<StyledDiv>
<Logo>
<Link to="/c/login" className="header-logo">
<img src={String(logo)} alt="logo" />
</Link>
</Logo>
{text}
{signUpButton}
{signInButton}
</StyledDiv>
</StyledNav>
);
}; };
interface ButtonProps { interface ButtonProps {
className?: string; className?: string;
} }
export const SignInButton = (props: ButtonProps): React.ReactElement => { export const SignInButton = (props: ButtonProps): React.ReactElement => {
return ( return (
<span className={`${props.className}`}> <span className={`${props.className}`}>
<Button color="primary" size="medium" variant="outlined" component={Link} to="/c/login"> <Button color="primary" size="medium" variant="outlined" component={Link} to="/c/login">
<FormattedMessage id="login.signin" defaultMessage="Sign In" /> <FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button> </Button>
</span> </span>
); );
}; };
const SignUpButton = (props: ButtonProps): React.ReactElement => { const SignUpButton = (props: ButtonProps): React.ReactElement => {
return ( return (
<span className={`${props.className}`}> <span className={`${props.className}`}>
<Button <Button
color="primary" color="primary"
size="medium" size="medium"
variant="outlined" variant="outlined"
component={Link} component={Link}
to="/c/registration" to="/c/registration"
> >
<FormattedMessage id="login.signup" defaultMessage="Sign Up" /> <FormattedMessage id="login.signup" defaultMessage="Sign Up" />
</Button> </Button>
</span> </span>
); );
}; };
export default Header; export default Header;

View File

@ -14,130 +14,130 @@ import ReactGA from 'react-ga4';
import { getCsrfToken, getCsrfTokenParameter } from '../../utils'; import { getCsrfToken, getCsrfTokenParameter } from '../../utils';
type ConfigStatusProps = { type ConfigStatusProps = {
enabled?: boolean; enabled?: boolean;
}; };
const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.ReactElement => { const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.ReactElement => {
let result; let result;
if (enabled === true) { if (enabled === true) {
result = ( result = (
<div className="db-warn-msg"> <div className="db-warn-msg">
<p> <p>
<FormattedMessage <FormattedMessage
id="login.hsqldbcofig" id="login.hsqldbcofig"
defaultMessage="Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL" defaultMessage="Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL"
description="Missing production database configured" description="Missing production database configured"
/> />
<a href="https://wisemapping.atlassian.net/wiki/display/WS/Database+Configuration"> <a href="https://wisemapping.atlassian.net/wiki/display/WS/Database+Configuration">
{' '} {' '}
here here
</a> </a>
</p> </p>
</div> </div>
); );
} }
return result || null; return result || null;
}; };
const LoginError = () => { const LoginError = () => {
// @Todo: This must be reviewed to be based on navigation state. // @Todo: This must be reviewed to be based on navigation state.
// Login error example: http://localhost:8080/c/login?login.error=2 // Login error example: http://localhost:8080/c/login?login.error=2
const errorCode = new URLSearchParams(window.location.search).get('login_error'); const errorCode = new URLSearchParams(window.location.search).get('login_error');
const intl = useIntl(); const intl = useIntl();
let msg: null | string = null; let msg: null | string = null;
if (errorCode) { if (errorCode) {
switch (errorCode) { switch (errorCode) {
case '3': case '3':
msg = intl.formatMessage({ msg = intl.formatMessage({
id: 'login.userinactive', id: 'login.userinactive',
defaultMessage: defaultMessage:
"Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!.", "Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!.",
}); });
break; break;
default: default:
msg = intl.formatMessage({ msg = intl.formatMessage({
id: 'login.error', id: 'login.error',
defaultMessage: 'The email address or password you entered is not valid.', defaultMessage: 'The email address or password you entered is not valid.',
}); });
}
} }
return msg ? <GlobalError error={{ msg: msg }} /> : null; }
return msg ? <GlobalError error={{ msg: msg }} /> : null;
}; };
const LoginPage = (): React.ReactElement => { const LoginPage = (): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
useEffect(() => { useEffect(() => {
document.title = intl.formatMessage({ id: 'login.page-title', defaultMessage: 'Login | WiseMapping' }); document.title = intl.formatMessage({
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Login' }); id: 'login.page-title',
}, []); defaultMessage: 'Login | WiseMapping',
});
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Login' });
}, []);
return ( return (
<div> <div>
<Header type="only-signup" /> <Header type="only-signup" />
<FormContainer maxWidth="xs"> <FormContainer maxWidth="xs">
<Typography variant="h4" component="h1"> <Typography variant="h4" component="h1">
<FormattedMessage id="login.title" defaultMessage="Welcome" /> <FormattedMessage id="login.title" defaultMessage="Welcome" />
</Typography> </Typography>
<Typography paragraph> <Typography paragraph>
<FormattedMessage id="login.desc" defaultMessage="Log into your account" /> <FormattedMessage id="login.desc" defaultMessage="Log into your account" />
</Typography> </Typography>
<LoginError /> <LoginError />
<FormControl> <FormControl>
<form action="/c/perform-login" method="POST"> <form action="/c/perform-login" method="POST">
<input type='hidden' value={getCsrfToken()} name={getCsrfTokenParameter()} /> <input type="hidden" value={getCsrfToken()} name={getCsrfTokenParameter()} />
<Input <Input
name="username" name="username"
type="email" type="email"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'login.email', id: 'login.email',
defaultMessage: 'Email', defaultMessage: 'Email',
})} })}
required required
autoComplete="email" autoComplete="email"
/> />
<Input <Input
name="password" name="password"
type="password" type="password"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'login.password', id: 'login.password',
defaultMessage: 'Password', defaultMessage: 'Password',
})} })}
required required
autoComplete="current-password" autoComplete="current-password"
/> />
<div> <div>
<input name="remember-me" id="remember-me" type="checkbox" /> <input name="remember-me" id="remember-me" type="checkbox" />
<label htmlFor="remember-me"> <label htmlFor="remember-me">
<FormattedMessage <FormattedMessage id="login.remberme" defaultMessage="Remember me" />
id="login.remberme" </label>
defaultMessage="Remember me" </div>
/> <SubmitButton
</label> value={intl.formatMessage({
</div> id: 'login.signin',
<SubmitButton defaultMessage: 'Sign In',
value={intl.formatMessage({ })}
id: 'login.signin', />
defaultMessage: 'Sign In', </form>
})} </FormControl>
/>
</form>
</FormControl>
<Link component={RouterLink} to="/c/forgot-password"> <Link component={RouterLink} to="/c/forgot-password">
<FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" /> <FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" />
</Link> </Link>
<ConfigStatusMessage /> <ConfigStatusMessage />
</FormContainer> </FormContainer>
<Footer /> <Footer />
</div> </div>
); );
}; };
export default LoginPage; export default LoginPage;

View File

@ -14,165 +14,168 @@ import FormGroup from '@mui/material/FormGroup';
import Switch from '@mui/material/Switch'; import Switch from '@mui/material/Switch';
type AccountInfoDialogProps = { type AccountInfoDialogProps = {
onClose: () => void; onClose: () => void;
}; };
type AccountInfoModel = { type AccountInfoModel = {
email: string; email: string;
firstname: string; firstname: string;
lastname: string; lastname: string;
}; };
const defaultModel: AccountInfoModel = { firstname: '', lastname: '', email: '' }; const defaultModel: AccountInfoModel = { firstname: '', lastname: '', email: '' };
const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElement => { const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElement => {
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [remove, setRemove] = React.useState<boolean>(false); const [remove, setRemove] = React.useState<boolean>(false);
const [model, setModel] = React.useState<AccountInfoModel>(defaultModel); const [model, setModel] = React.useState<AccountInfoModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl(); const intl = useIntl();
const mutationChangeName = useMutation<void, ErrorInfo, AccountInfoModel>( const mutationChangeName = useMutation<void, ErrorInfo, AccountInfoModel>(
(model: AccountInfoModel) => { (model: AccountInfoModel) => {
return client.updateAccountInfo(model.firstname, model.lastname); return client.updateAccountInfo(model.firstname, model.lastname);
}, },
{ {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries('account'); queryClient.invalidateQueries('account');
onClose();
},
onError: (error) => {
setError(error);
},
}
);
const mutationRemove = useMutation<void, ErrorInfo, void>(
() => {
return client.deleteAccount();
},
{
onSuccess: () => {
window.location.href = '/c/login';
onClose();
},
onError: (error) => {
setError(error);
},
}
);
const account = fetchAccount();
useEffect(() => {
if (account) {
setModel({
email: account?.email,
lastname: account?.lastname,
firstname: account?.firstname,
});
}
}, [account?.email]);
const handleOnClose = (): void => {
onClose(); onClose();
setModel(defaultModel); },
setError(undefined); onError: (error) => {
}; setError(error);
},
},
);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const mutationRemove = useMutation<void, ErrorInfo, void>(
event.preventDefault(); () => {
if (remove) { return client.deleteAccount();
mutationRemove.mutate(); },
} else { {
mutationChangeName.mutate(model); onSuccess: () => {
} window.location.href = '/c/login';
}; onClose();
},
onError: (error) => {
setError(error);
},
},
);
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const account = fetchAccount();
event.preventDefault(); useEffect(() => {
if (account) {
setModel({
email: account?.email,
lastname: account?.lastname,
firstname: account?.firstname,
});
}
}, [account?.email]);
const name = event.target.name; const handleOnClose = (): void => {
const value = event.target.value; onClose();
setModel({ ...model, [name as keyof AccountInfoModel]: value }); setModel(defaultModel);
}; setError(undefined);
};
const handleOnRemoveChange = (event) => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
setRemove(event.target.checked); event.preventDefault();
}; if (remove) {
mutationRemove.mutate();
} else {
mutationChangeName.mutate(model);
}
};
return ( const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
<BaseDialog event.preventDefault();
onClose={handleOnClose}
onSubmit={handleOnSubmit} const name = event.target.name;
error={error} const value = event.target.value;
title={intl.formatMessage({ id: 'accountinfo.title', defaultMessage: 'Account info' })} setModel({ ...model, [name as keyof AccountInfoModel]: value });
submitButton={intl.formatMessage({ };
id: 'accountinfo.button',
defaultMessage: 'Accept', const handleOnRemoveChange = (event) => {
setRemove(event.target.checked);
};
return (
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({ id: 'accountinfo.title', defaultMessage: 'Account info' })}
submitButton={intl.formatMessage({
id: 'accountinfo.button',
defaultMessage: 'Accept',
})}
>
<FormControl fullWidth={true}>
<Input
name="email"
type="text"
disabled={true}
label={intl.formatMessage({ id: 'accountinfo.email', defaultMessage: 'Email' })}
value={model.email}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input
name="firstname"
type="text"
label={intl.formatMessage({
id: 'accountinfo.firstname',
defaultMessage: 'First Name',
})}
value={model.firstname}
onChange={handleOnChange}
required={true}
fullWidth={true}
/>
<Input
name="lastname"
type="text"
label={intl.formatMessage({
id: 'accountinfo.lastname',
defaultMessage: 'Last Name',
})}
value={model.lastname}
onChange={handleOnChange}
required={true}
fullWidth={true}
/>
<FormGroup>
{remove && (
<Alert severity="error">
<FormattedMessage
id="account.delete-warning"
defaultMessage="Keep in mind that you will not be able retrieve any mindmap you have added. All your information will be deleted and it can not be restored."
/>
</Alert>
)}
<FormControlLabel
control={
<Switch
checked={remove}
onChange={handleOnRemoveChange}
name="remove"
color="primary"
/>
}
label={intl.formatMessage({
id: 'accountinfo.deleteaccount',
defaultMessage: 'Delete Account',
})} })}
> />
<FormControl fullWidth={true}> </FormGroup>
<Input </FormControl>
name="email" </BaseDialog>
type="text" );
disabled={true}
label={intl.formatMessage({ id: 'accountinfo.email', defaultMessage: 'Email' })}
value={model.email}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input
name="firstname"
type="text"
label={intl.formatMessage({
id: 'accountinfo.firstname',
defaultMessage: 'First Name',
})}
value={model.firstname}
onChange={handleOnChange}
required={true}
fullWidth={true}
/>
<Input
name="lastname"
type="text"
label={intl.formatMessage({
id: 'accountinfo.lastname',
defaultMessage: 'Last Name',
})}
value={model.lastname}
onChange={handleOnChange}
required={true}
fullWidth={true}
/>
<FormGroup>
{remove && (
<Alert severity="error">
<FormattedMessage
id="account.delete-warning"
defaultMessage="Keep in mind that you will not be able retrieve any mindmap you have added. All your information will be deleted and it can not be restored."
/>
</Alert>
)}
<FormControlLabel
control={
<Switch
checked={remove}
onChange={handleOnRemoveChange}
name="remove"
color="primary"
/>
}
label={intl.formatMessage({ id: 'accountinfo.deleteaccount', defaultMessage: 'Delete Account' })}
/>
</FormGroup>
</FormControl>
</BaseDialog>
);
}; };
export default AccountInfoDialog; export default AccountInfoDialog;

View File

@ -9,108 +9,108 @@ import { useSelector } from 'react-redux';
import { activeInstance } from '../../../../redux/clientSlice'; import { activeInstance } from '../../../../redux/clientSlice';
type ChangePasswordDialogProps = { type ChangePasswordDialogProps = {
onClose: () => void; onClose: () => void;
}; };
type ChangePasswordModel = { type ChangePasswordModel = {
password: string; password: string;
retryPassword: string; retryPassword: string;
}; };
const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' }; const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' };
const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps): React.ReactElement => { const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps): React.ReactElement => {
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ChangePasswordModel>(defaultModel); const [model, setModel] = React.useState<ChangePasswordModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl(); const intl = useIntl();
const mutation = useMutation<void, ErrorInfo, ChangePasswordModel>( const mutation = useMutation<void, ErrorInfo, ChangePasswordModel>(
(model: ChangePasswordModel) => { (model: ChangePasswordModel) => {
return client.updateAccountPassword(model.password); return client.updateAccountPassword(model.password);
}, },
{ {
onSuccess: () => { onSuccess: () => {
onClose();
},
onError: (error) => {
setError(error);
},
}
);
const handleOnClose = (): void => {
onClose(); onClose();
setModel(defaultModel); },
setError(undefined); onError: (error) => {
}; setError(error);
},
},
);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnClose = (): void => {
event.preventDefault(); onClose();
setModel(defaultModel);
setError(undefined);
};
// Check password are equal ... const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
if (model.password != model.retryPassword) { event.preventDefault();
setError({
msg: intl.formatMessage({
id: 'changepwd.password-match',
defaultMessage: 'Password do not match. Please, try again.',
}),
});
return;
}
mutation.mutate(model); // Check password are equal ...
}; if (model.password != model.retryPassword) {
setError({
msg: intl.formatMessage({
id: 'changepwd.password-match',
defaultMessage: 'Password do not match. Please, try again.',
}),
});
return;
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { mutation.mutate(model);
event.preventDefault(); };
const name = event.target.name; const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const value = event.target.value; event.preventDefault();
setModel({ ...model, [name as keyof ChangePasswordModel]: value });
};
return ( const name = event.target.name;
<BaseDialog const value = event.target.value;
onClose={handleOnClose} setModel({ ...model, [name as keyof ChangePasswordModel]: value });
onSubmit={handleOnSubmit} };
error={error}
title={intl.formatMessage({ id: 'changepwd.title', defaultMessage: 'Change Password' })}
description={intl.formatMessage({
id: 'changepwd.description',
defaultMessage: 'Please, provide the new password for your account.',
})}
submitButton={intl.formatMessage({ id: 'changepwd.button', defaultMessage: 'Change' })}
>
<FormControl fullWidth={true}>
<Input
name="password"
type="password"
label={intl.formatMessage({
id: 'changepwd.password',
defaultMessage: 'Password',
})}
value={model.password}
onChange={handleOnChange}
error={error}
fullWidth={true}
autoComplete="new-password"
/>
<Input return (
name="retryPassword" <BaseDialog
type="password" onClose={handleOnClose}
label={intl.formatMessage({ onSubmit={handleOnSubmit}
id: 'changepwd.confirm-password', error={error}
defaultMessage: 'Confirm Password', title={intl.formatMessage({ id: 'changepwd.title', defaultMessage: 'Change Password' })}
})} description={intl.formatMessage({
value={model.retryPassword} id: 'changepwd.description',
onChange={handleOnChange} defaultMessage: 'Please, provide the new password for your account.',
required={true} })}
fullWidth={true} submitButton={intl.formatMessage({ id: 'changepwd.button', defaultMessage: 'Change' })}
autoComplete="new-password" >
/> <FormControl fullWidth={true}>
</FormControl> <Input
</BaseDialog> name="password"
); type="password"
label={intl.formatMessage({
id: 'changepwd.password',
defaultMessage: 'Password',
})}
value={model.password}
onChange={handleOnChange}
error={error}
fullWidth={true}
autoComplete="new-password"
/>
<Input
name="retryPassword"
type="password"
label={intl.formatMessage({
id: 'changepwd.confirm-password',
defaultMessage: 'Confirm Password',
})}
value={model.retryPassword}
onChange={handleOnChange}
required={true}
fullWidth={true}
autoComplete="new-password"
/>
</FormControl>
</BaseDialog>
);
}; };
export default ChangePasswordDialog; export default ChangePasswordDialog;

View File

@ -16,90 +16,86 @@ import ExitToAppOutlined from '@mui/icons-material/ExitToAppOutlined';
type ActionType = 'change-password' | 'account-info' | undefined; type ActionType = 'change-password' | 'account-info' | undefined;
const AccountMenu = (): React.ReactElement => { const AccountMenu = (): React.ReactElement => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const [action, setAction] = React.useState<ActionType>(undefined); const [action, setAction] = React.useState<ActionType>(undefined);
const handleMenu = (event: React.MouseEvent<HTMLElement>) => { const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
const handleLogout = (event: MouseEvent) => { const handleLogout = (event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
const elem = document.getElementById('logoutFrom') as HTMLFormElement; const elem = document.getElementById('logoutFrom') as HTMLFormElement;
elem.submit(); elem.submit();
}; };
const account = fetchAccount(); const account = fetchAccount();
return ( return (
<span> <span>
<Tooltip <Tooltip
arrow={true} arrow={true}
title={`${account?.firstname} ${account?.lastname} <${account?.email}>`} title={`${account?.firstname} ${account?.lastname} <${account?.email}>`}
> >
<IconButton onClick={handleMenu} size="large"> <IconButton onClick={handleMenu} size="large">
<AccountCircle fontSize="large" style={{ color: 'black' }} /> <AccountCircle fontSize="large" style={{ color: 'black' }} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Menu <Menu
id="appbar-profile" id="appbar-profile"
anchorEl={anchorEl} anchorEl={anchorEl}
keepMounted keepMounted
open={open} open={open}
onClose={handleClose} onClose={handleClose}
anchorOrigin={{ anchorOrigin={{
vertical: 'bottom', vertical: 'bottom',
horizontal: 'right', horizontal: 'right',
}} }}
transformOrigin={{ transformOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'right', horizontal: 'right',
}} }}
> >
<MenuItem <MenuItem
onClick={() => { onClick={() => {
handleClose(), setAction('account-info'); handleClose(), setAction('account-info');
}} }}
> >
<ListItemIcon> <ListItemIcon>
<SettingsApplicationsOutlined fontSize="small" /> <SettingsApplicationsOutlined fontSize="small" />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="menu.account" defaultMessage="Account" /> <FormattedMessage id="menu.account" defaultMessage="Account" />
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
handleClose(), setAction('change-password'); handleClose(), setAction('change-password');
}} }}
> >
<ListItemIcon> <ListItemIcon>
<LockOpenOutlined fontSize="small" /> <LockOpenOutlined fontSize="small" />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="menu.change-password" defaultMessage="Change password" /> <FormattedMessage id="menu.change-password" defaultMessage="Change password" />
</MenuItem> </MenuItem>
<MenuItem onClick={handleClose}> <MenuItem onClick={handleClose}>
<form action="/c/logout" method='POST' id="logoutFrom"></form> <form action="/c/logout" method="POST" id="logoutFrom"></form>
<Link color="textSecondary" href="/c/logout" onClick={(e) => handleLogout(e)}> <Link color="textSecondary" href="/c/logout" onClick={(e) => handleLogout(e)}>
<ListItemIcon> <ListItemIcon>
<ExitToAppOutlined fontSize="small" /> <ExitToAppOutlined fontSize="small" />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="menu.signout" defaultMessage="Sign Out" /> <FormattedMessage id="menu.signout" defaultMessage="Sign Out" />
</Link> </Link>
</MenuItem> </MenuItem>
</Menu> </Menu>
{ {action == 'change-password' && <ChangePasswordDialog onClose={() => setAction(undefined)} />}
action == 'change-password' && ( {action == 'account-info' && <AccountInfoDialog onClose={() => setAction(undefined)} />}
<ChangePasswordDialog onClose={() => setAction(undefined)} /> </span>
) );
}
{action == 'account-info' && <AccountInfoDialog onClose={() => setAction(undefined)} />}
</span >
);
}; };
export default AccountMenu; export default AccountMenu;

View File

@ -18,139 +18,139 @@ import MenuItem from '@mui/material/MenuItem';
import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemIcon from '@mui/material/ListItemIcon';
import Divider from '@mui/material/Divider'; import Divider from '@mui/material/Divider';
export type ActionType = export type ActionType =
| 'open' | 'open'
| 'share' | 'share'
| 'import' | 'import'
| 'delete' | 'delete'
| 'info' | 'info'
| 'create' | 'create'
| 'duplicate' | 'duplicate'
| 'export' | 'export'
| 'label' | 'label'
| 'rename' | 'rename'
| 'print' | 'print'
| 'info' | 'info'
| 'publish' | 'publish'
| 'history' | 'history'
| undefined; | undefined;
interface ActionProps { interface ActionProps {
onClose: (action: ActionType) => void; onClose: (action: ActionType) => void;
anchor?: HTMLElement; anchor?: HTMLElement;
mapId?: number; mapId?: number;
} }
const ActionChooser = (props: ActionProps): React.ReactElement => { const ActionChooser = (props: ActionProps): React.ReactElement => {
const { anchor, onClose, mapId } = props; const { anchor, onClose, mapId } = props;
const handleOnClose = ( const handleOnClose = (
action: ActionType action: ActionType,
): ((event: React.MouseEvent<HTMLLIElement>) => void) => { ): ((event: React.MouseEvent<HTMLLIElement>) => void) => {
return (event): void => { return (event): void => {
event.stopPropagation(); event.stopPropagation();
onClose(action); onClose(action);
};
}; };
};
const role = mapId ? fetchMapById(mapId)?.map?.role : undefined; const role = mapId ? fetchMapById(mapId)?.map?.role : undefined;
return ( return (
<Menu <Menu
anchorEl={anchor} anchorEl={anchor}
keepMounted keepMounted
open={Boolean(anchor)} open={Boolean(anchor)}
onClose={handleOnClose(undefined)} onClose={handleOnClose(undefined)}
elevation={1} elevation={1}
> >
<MenuItem onClick={handleOnClose('open')} style={{ width: '220px' }}> <MenuItem onClick={handleOnClose('open')} style={{ width: '220px' }}>
<ListItemIcon> <ListItemIcon>
<DescriptionOutlinedIcon /> <DescriptionOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.open" defaultMessage="Open" /> <FormattedMessage id="action.open" defaultMessage="Open" />
</MenuItem> </MenuItem>
<Divider /> <Divider />
<MenuItem onClick={handleOnClose('duplicate')}> <MenuItem onClick={handleOnClose('duplicate')}>
<ListItemIcon> <ListItemIcon>
<FileCopyOutlinedIcon /> <FileCopyOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.duplicate" defaultMessage="Duplicate" /> <FormattedMessage id="action.duplicate" defaultMessage="Duplicate" />
</MenuItem> </MenuItem>
{role == 'owner' && ( {role == 'owner' && (
<MenuItem onClick={handleOnClose('rename')}> <MenuItem onClick={handleOnClose('rename')}>
<ListItemIcon> <ListItemIcon>
<EditOutlinedIcon /> <EditOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.rename" defaultMessage="Rename" /> <FormattedMessage id="action.rename" defaultMessage="Rename" />
</MenuItem> </MenuItem>
)} )}
<MenuItem onClick={handleOnClose('label')}> <MenuItem onClick={handleOnClose('label')}>
<ListItemIcon> <ListItemIcon>
<LabelOutlined /> <LabelOutlined />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.label" defaultMessage="Add Label" /> <FormattedMessage id="action.label" defaultMessage="Add Label" />
</MenuItem> </MenuItem>
<MenuItem onClick={handleOnClose('delete')}> <MenuItem onClick={handleOnClose('delete')}>
<ListItemIcon> <ListItemIcon>
<DeleteOutlinedIcon /> <DeleteOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.delete" defaultMessage="Delete" /> <FormattedMessage id="action.delete" defaultMessage="Delete" />
</MenuItem> </MenuItem>
<Divider /> <Divider />
<MenuItem onClick={handleOnClose('export')}> <MenuItem onClick={handleOnClose('export')}>
<ListItemIcon> <ListItemIcon>
<CloudDownloadOutlinedIcon /> <CloudDownloadOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.export" defaultMessage="Export" /> <FormattedMessage id="action.export" defaultMessage="Export" />
</MenuItem> </MenuItem>
<MenuItem onClick={handleOnClose('print')}> <MenuItem onClick={handleOnClose('print')}>
<ListItemIcon> <ListItemIcon>
<PrintOutlinedIcon /> <PrintOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.print" defaultMessage="Print" /> <FormattedMessage id="action.print" defaultMessage="Print" />
</MenuItem> </MenuItem>
{role == 'owner' && ( {role == 'owner' && (
<MenuItem onClick={handleOnClose('publish')}> <MenuItem onClick={handleOnClose('publish')}>
<ListItemIcon> <ListItemIcon>
<PublicOutlinedIcon /> <PublicOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.publish" defaultMessage="Publish" /> <FormattedMessage id="action.publish" defaultMessage="Publish" />
</MenuItem> </MenuItem>
)} )}
{role == 'owner' && ( {role == 'owner' && (
<MenuItem onClick={handleOnClose('share')}> <MenuItem onClick={handleOnClose('share')}>
<ListItemIcon> <ListItemIcon>
<ShareOutlinedIcon /> <ShareOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.share" defaultMessage="Share" /> <FormattedMessage id="action.share" defaultMessage="Share" />
</MenuItem> </MenuItem>
)} )}
<Divider /> <Divider />
<MenuItem onClick={handleOnClose('info')}> <MenuItem onClick={handleOnClose('info')}>
<ListItemIcon> <ListItemIcon>
<InfoOutlinedIcon /> <InfoOutlinedIcon />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.info" defaultMessage="Info" /> <FormattedMessage id="action.info" defaultMessage="Info" />
</MenuItem> </MenuItem>
{role != 'viewer' && ( {role != 'viewer' && (
<MenuItem onClick={handleOnClose('history')}> <MenuItem onClick={handleOnClose('history')}>
<ListItemIcon> <ListItemIcon>
<HistoryOutlined/> <HistoryOutlined />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="action.history" defaultMessage="History" /> <FormattedMessage id="action.history" defaultMessage="History" />
</MenuItem> </MenuItem>
)} )}
</Menu> </Menu>
); );
}; };
export default ActionChooser; export default ActionChooser;

View File

@ -8,89 +8,89 @@ import { StyledButton, NewLabelContainer, NewLabelColor, CreateLabel } from './s
import { Tooltip } from '@mui/material'; import { Tooltip } from '@mui/material';
const labelColors = [ const labelColors = [
'#00b327', '#00b327',
'#0565ff', '#0565ff',
'#2d2dd6', '#2d2dd6',
'#6a00ba', '#6a00ba',
'#ad1599', '#ad1599',
'#ff1e35', '#ff1e35',
'#ff6600', '#ff6600',
'#ffff47', '#ffff47',
]; ];
type AddLabelFormProps = { type AddLabelFormProps = {
onAdd: (newLabel: Label) => void; onAdd: (newLabel: Label) => void;
}; };
const AddLabelDialog = ({ onAdd }: AddLabelFormProps): React.ReactElement => { const AddLabelDialog = ({ onAdd }: AddLabelFormProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const [createLabelColorIndex, setCreateLabelColorIndex] = React.useState( const [createLabelColorIndex, setCreateLabelColorIndex] = React.useState(
Math.floor(Math.random() * labelColors.length) Math.floor(Math.random() * labelColors.length),
); );
const [newLabelTitle, setNewLabelTitle] = React.useState(''); const [newLabelTitle, setNewLabelTitle] = React.useState('');
const newLabelColor = labelColors[createLabelColorIndex]; const newLabelColor = labelColors[createLabelColorIndex];
const setNextLabelColorIndex = () => { const setNextLabelColorIndex = () => {
const nextIndex = labelColors[createLabelColorIndex + 1] ? createLabelColorIndex + 1 : 0; const nextIndex = labelColors[createLabelColorIndex + 1] ? createLabelColorIndex + 1 : 0;
setCreateLabelColorIndex(nextIndex); setCreateLabelColorIndex(nextIndex);
}; };
const handleSubmitNew = () => { const handleSubmitNew = () => {
onAdd({ onAdd({
title: newLabelTitle, title: newLabelTitle,
color: newLabelColor, color: newLabelColor,
id: 0, id: 0,
}); });
setNewLabelTitle(''); setNewLabelTitle('');
setNextLabelColorIndex(); setNextLabelColorIndex();
}; };
return ( return (
<CreateLabel> <CreateLabel>
<NewLabelContainer> <NewLabelContainer>
<Tooltip <Tooltip
arrow={true} arrow={true}
title={intl.formatMessage({ title={intl.formatMessage({
id: 'label.change-color', id: 'label.change-color',
defaultMessage: 'Change label color', defaultMessage: 'Change label color',
})} })}
> >
<NewLabelColor <NewLabelColor
htmlColor={newLabelColor} htmlColor={newLabelColor}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setNextLabelColorIndex(); setNextLabelColorIndex();
}} }}
/> />
</Tooltip> </Tooltip>
<TextField <TextField
variant="standard" variant="standard"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'label.add-placeholder', id: 'label.add-placeholder',
defaultMessage: 'Label title', defaultMessage: 'Label title',
})} })}
onChange={(e) => setNewLabelTitle(e.target.value)} onChange={(e) => setNewLabelTitle(e.target.value)}
onKeyPress={(e) => { onKeyPress={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
handleSubmitNew(); handleSubmitNew();
} }
}} }}
value={newLabelTitle} value={newLabelTitle}
/> />
<StyledButton <StyledButton
onClick={() => handleSubmitNew()} onClick={() => handleSubmitNew()}
disabled={!newLabelTitle.length} disabled={!newLabelTitle.length}
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id: 'label.add-button', id: 'label.add-button',
defaultMessage: 'Add label', defaultMessage: 'Add label',
})} })}
> >
<AddIcon /> <AddIcon />
</StyledButton> </StyledButton>
</NewLabelContainer> </NewLabelContainer>
</CreateLabel> </CreateLabel>
); );
} };
export default AddLabelDialog; export default AddLabelDialog;

View File

@ -10,85 +10,82 @@ import { useDispatch } from 'react-redux';
import { disableHotkeys, enableHotkeys } from '../../../../redux/editorSlice'; import { disableHotkeys, enableHotkeys } from '../../../../redux/editorSlice';
export type DialogProps = { export type DialogProps = {
onClose: () => void; onClose: () => void;
onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void; onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;
children: unknown; children: unknown;
error?: ErrorInfo; error?: ErrorInfo;
title: string; title: string;
description?: string; description?: string;
submitButton?: string; submitButton?: string;
actionUrl?: string; actionUrl?: string;
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false; maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
PaperProps?: Partial<PaperProps>; PaperProps?: Partial<PaperProps>;
}; };
const BaseDialog = (props: DialogProps): React.ReactElement => { const BaseDialog = (props: DialogProps): React.ReactElement => {
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
dispatch(disableHotkeys()); dispatch(disableHotkeys());
return () => { return () => {
dispatch(enableHotkeys()) dispatch(enableHotkeys());
};
}, []);
const { onClose, onSubmit, maxWidth = 'sm', PaperProps } = props;
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (onSubmit) {
onSubmit(e);
}
}; };
}, []);
const { onClose, onSubmit, maxWidth = 'sm', PaperProps } = props;
const description = props.description ? ( const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
<DialogContentText>{props.description}</DialogContentText> e.preventDefault();
) : null; if (onSubmit) {
return ( onSubmit(e);
<div> }
<StyledDialog };
open={true}
onClose={onClose}
maxWidth={maxWidth}
PaperProps={PaperProps}
fullWidth={true}
>
<form autoComplete="off" onSubmit={handleOnSubmit}>
<StyledDialogTitle>{props.title}</StyledDialogTitle>
<StyledDialogContent> const description = props.description ? (
{description} <DialogContentText>{props.description}</DialogContentText>
<GlobalError error={props.error} /> ) : null;
{props.children} return (
</StyledDialogContent> <div>
<StyledDialog
open={true}
onClose={onClose}
maxWidth={maxWidth}
PaperProps={PaperProps}
fullWidth={true}
>
<form autoComplete="off" onSubmit={handleOnSubmit}>
<StyledDialogTitle>{props.title}</StyledDialogTitle>
<StyledDialogActions> <StyledDialogContent>
<Button type="button" color="primary" size="medium" onClick={onClose}> {description}
{onSubmit ? ( <GlobalError error={props.error} />
<FormattedMessage {props.children}
id="action.cancel-button" </StyledDialogContent>
defaultMessage="Cancel"
/> <StyledDialogActions>
) : ( <Button type="button" color="primary" size="medium" onClick={onClose}>
<FormattedMessage id="action.close-button" defaultMessage="Close" /> {onSubmit ? (
)} <FormattedMessage id="action.cancel-button" defaultMessage="Cancel" />
</Button> ) : (
{onSubmit && ( <FormattedMessage id="action.close-button" defaultMessage="Close" />
<Button )}
color="primary" </Button>
size="medium" {onSubmit && (
variant="contained" <Button
type="submit" color="primary"
disableElevation={true} size="medium"
> variant="contained"
{props.submitButton} type="submit"
</Button> disableElevation={true}
)} >
</StyledDialogActions> {props.submitButton}
</form> </Button>
</StyledDialog> )}
</div> </StyledDialogActions>
); </form>
</StyledDialog>
</div>
);
}; };
export default BaseDialog; export default BaseDialog;

View File

@ -10,102 +10,102 @@ import Input from '../../../form/input';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';
export type CreateModel = { export type CreateModel = {
title: string; title: string;
description?: string; description?: string;
}; };
export type CreateProps = { export type CreateProps = {
onClose: () => void; onClose: () => void;
}; };
const defaultModel: CreateModel = { title: '', description: '' }; const defaultModel: CreateModel = { title: '', description: '' };
const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => { const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => {
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<CreateModel>(defaultModel); const [model, setModel] = React.useState<CreateModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl(); const intl = useIntl();
const mutation = useMutation<number, ErrorInfo, CreateModel>( const mutation = useMutation<number, ErrorInfo, CreateModel>(
(model: CreateModel) => { (model: CreateModel) => {
return client.createMap(model); return client.createMap(model);
}, },
{ {
onSuccess: (mapId: number) => { onSuccess: (mapId: number) => {
window.location.href = `/c/maps/${mapId}/edit`; window.location.href = `/c/maps/${mapId}/edit`;
}, },
onError: (error) => { onError: (error) => {
setError(error); setError(error);
}, },
} },
); );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose(); onClose();
setModel(defaultModel); setModel(defaultModel);
setError(undefined); setError(undefined);
}; };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault(); event.preventDefault();
mutation.mutate(model); mutation.mutate(model);
}; };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault(); event.preventDefault();
const name = event.target.name; const name = event.target.name;
const value = event.target.value; const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value }); setModel({ ...model, [name as keyof BasicMapInfo]: value });
}; };
return ( return (
<div> <div>
<BaseDialog <BaseDialog
onClose={handleOnClose} onClose={handleOnClose}
onSubmit={handleOnSubmit} onSubmit={handleOnSubmit}
error={error} error={error}
title={intl.formatMessage({ title={intl.formatMessage({
id: 'create.title', id: 'create.title',
defaultMessage: 'Create a new mindmap', defaultMessage: 'Create a new mindmap',
})} })}
description={intl.formatMessage({ description={intl.formatMessage({
id: 'create.description', id: 'create.description',
defaultMessage: 'Please, fill the new map name and description.', defaultMessage: 'Please, fill the new map name and description.',
})} })}
submitButton={intl.formatMessage({ id: 'create.button', defaultMessage: 'Create' })} submitButton={intl.formatMessage({ id: 'create.button', defaultMessage: 'Create' })}
> >
<FormControl fullWidth={true}> <FormControl fullWidth={true}>
<Input <Input
name="title" name="title"
type="text" type="text"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'action.rename-name-placeholder', id: 'action.rename-name-placeholder',
defaultMessage: 'Name', defaultMessage: 'Name',
})} })}
value={model.title} value={model.title}
onChange={handleOnChange} onChange={handleOnChange}
error={error} error={error}
fullWidth={true} fullWidth={true}
maxLength={60} maxLength={60}
/> />
<Input <Input
name="description" name="description"
type="text" type="text"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'action.rename-description-placeholder', id: 'action.rename-description-placeholder',
defaultMessage: 'Description', defaultMessage: 'Description',
})} })}
value={model.description} value={model.description}
onChange={handleOnChange} onChange={handleOnChange}
required={false} required={false}
fullWidth={true} fullWidth={true}
rows={3} rows={3}
/> />
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
</div> </div>
); );
}; };
export default CreateDialog; export default CreateDialog;

View File

@ -10,50 +10,53 @@ import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle'; import AlertTitle from '@mui/material/AlertTitle';
const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const mutation = useMutation((id: number) => client.deleteMap(id), { const mutation = useMutation((id: number) => client.deleteMap(id), {
onSuccess: () => handleOnMutationSuccess(() => onClose(true), queryClient), onSuccess: () => handleOnMutationSuccess(() => onClose(true), queryClient),
onError: (error: ErrorInfo) => { onError: (error: ErrorInfo) => {
setError(error); setError(error);
}, },
}); });
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose(); onClose();
}; };
const handleOnSubmit = (): void => { const handleOnSubmit = (): void => {
mutation.mutate(mapId); mutation.mutate(mapId);
}; };
const { map } = fetchMapById(mapId) const { map } = fetchMapById(mapId);
const alertTitle = `${intl.formatMessage({ id: 'action.delete-title', defaultMessage: 'Delete' })} ${map?.title}`; const alertTitle = `${intl.formatMessage({
return ( id: 'action.delete-title',
<div> defaultMessage: 'Delete',
<BaseDialog })} ${map?.title}`;
error={error} return (
onClose={handleOnClose} <div>
onSubmit={handleOnSubmit} <BaseDialog
title={intl.formatMessage({ id: 'action.delete-title', defaultMessage: 'Delete' })} error={error}
submitButton={intl.formatMessage({ onClose={handleOnClose}
id: 'action.delete-title', onSubmit={handleOnSubmit}
defaultMessage: 'Delete', title={intl.formatMessage({ id: 'action.delete-title', defaultMessage: 'Delete' })}
})} submitButton={intl.formatMessage({
> id: 'action.delete-title',
<Alert severity="warning"> defaultMessage: 'Delete',
<AlertTitle>{alertTitle}</AlertTitle> })}
<FormattedMessage >
id="action.delete-description" <Alert severity="warning">
defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?." <AlertTitle>{alertTitle}</AlertTitle>
/> <FormattedMessage
</Alert> id="action.delete-description"
</BaseDialog> defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?."
</div> />
); </Alert>
</BaseDialog>
</div>
);
}; };
export default DeleteDialog; export default DeleteDialog;

View File

@ -9,55 +9,52 @@ import BaseDialog from '../base-dialog';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle'; import AlertTitle from '@mui/material/AlertTitle';
const DeleteMultiselectDialog = ({ const DeleteMultiselectDialog = ({ onClose, mapsId }: MultiDialogProps): React.ReactElement => {
onClose, const intl = useIntl();
mapsId, const client: Client = useSelector(activeInstance);
}: MultiDialogProps): React.ReactElement => { const queryClient = useQueryClient();
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const mutation = useMutation((ids: number[]) => client.deleteMaps(ids), { const mutation = useMutation((ids: number[]) => client.deleteMaps(ids), {
onSuccess: () => handleOnMutationSuccess(() => onClose(true), queryClient), onSuccess: () => handleOnMutationSuccess(() => onClose(true), queryClient),
onError: (error) => { onError: (error) => {
console.error(`Unexpected error ${error}`); console.error(`Unexpected error ${error}`);
}, },
}); });
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose(); onClose();
}; };
const handleOnSubmit = (): void => { const handleOnSubmit = (): void => {
mutation.mutate(mapsId); mutation.mutate(mapsId);
}; };
return ( return (
<div> <div>
<BaseDialog <BaseDialog
onClose={handleOnClose} onClose={handleOnClose}
onSubmit={handleOnSubmit} onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: 'action.delete-title', defaultMessage: 'Delete' })} title={intl.formatMessage({ id: 'action.delete-title', defaultMessage: 'Delete' })}
submitButton={intl.formatMessage({ submitButton={intl.formatMessage({
id: 'action.delete-title', id: 'action.delete-title',
defaultMessage: 'Delete', defaultMessage: 'Delete',
})} })}
> >
<Alert severity="warning"> <Alert severity="warning">
<AlertTitle> <AlertTitle>
<FormattedMessage <FormattedMessage
id="deletem.title" id="deletem.title"
defaultMessage="All selected maps will be deleted" defaultMessage="All selected maps will be deleted"
/> />
</AlertTitle> </AlertTitle>
<FormattedMessage <FormattedMessage
id="action.delete-description" id="action.delete-description"
defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?." defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?."
/> />
</Alert> </Alert>
</BaseDialog> </BaseDialog>
</div> </div>
); );
}; };
export default DeleteMultiselectDialog; export default DeleteMultiselectDialog;

View File

@ -11,104 +11,104 @@ import { SimpleDialogProps } from '..';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';
export type DuplicateModel = { export type DuplicateModel = {
id: number; id: number;
title: string; title: string;
description?: string; description?: string;
}; };
const defaultModel: DuplicateModel = { title: '', description: '', id: -1 }; const defaultModel: DuplicateModel = { title: '', description: '', id: -1 };
const DuplicateDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const DuplicateDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const service: Client = useSelector(activeInstance); const service: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<DuplicateModel>(defaultModel); const [model, setModel] = React.useState<DuplicateModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl(); const intl = useIntl();
const mutation = useMutation<number, ErrorInfo, DuplicateModel>( const mutation = useMutation<number, ErrorInfo, DuplicateModel>(
(model: DuplicateModel) => { (model: DuplicateModel) => {
const { id, ...rest } = model; const { id, ...rest } = model;
return service.duplicateMap(id, rest); return service.duplicateMap(id, rest);
}, },
{ {
onSuccess: (mapId) => { onSuccess: (mapId) => {
window.location.href = `/c/maps/${mapId}/edit`; window.location.href = `/c/maps/${mapId}/edit`;
}, },
onError: (error) => { onError: (error) => {
setError(error); setError(error);
}, },
} },
); );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose(); onClose();
}; };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault(); event.preventDefault();
mutation.mutate(model); mutation.mutate(model);
}; };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault(); event.preventDefault();
const name = event.target.name; const name = event.target.name;
const value = event.target.value; const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value }); setModel({ ...model, [name as keyof BasicMapInfo]: value });
}; };
const { map } = fetchMapById(mapId); const { map } = fetchMapById(mapId);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
setModel(map); setModel(map);
} }
}, [mapId]); }, [mapId]);
return ( return (
<div> <div>
<BaseDialog <BaseDialog
onClose={handleOnClose} onClose={handleOnClose}
onSubmit={handleOnSubmit} onSubmit={handleOnSubmit}
error={error} error={error}
title={intl.formatMessage({ id: 'duplicate.title', defaultMessage: 'Duplicate' })} title={intl.formatMessage({ id: 'duplicate.title', defaultMessage: 'Duplicate' })}
description={intl.formatMessage({ description={intl.formatMessage({
id: 'rename.description', id: 'rename.description',
defaultMessage: 'Please, fill the new map name and description.', defaultMessage: 'Please, fill the new map name and description.',
})} })}
submitButton={intl.formatMessage({ submitButton={intl.formatMessage({
id: 'duplicate.title', id: 'duplicate.title',
defaultMessage: 'Duplicate', defaultMessage: 'Duplicate',
})} })}
> >
<FormControl fullWidth={true}> <FormControl fullWidth={true}>
<Input <Input
name="title" name="title"
type="text" type="text"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'action.rename-name-placeholder', id: 'action.rename-name-placeholder',
defaultMessage: 'Name', defaultMessage: 'Name',
})} })}
value={model.title} value={model.title}
onChange={handleOnChange} onChange={handleOnChange}
error={error} error={error}
fullWidth={true} fullWidth={true}
/> />
<Input <Input
name="description" name="description"
type="text" type="text"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'action.rename-description-placeholder', id: 'action.rename-description-placeholder',
defaultMessage: 'Description', defaultMessage: 'Description',
})} })}
value={model.description} value={model.description}
onChange={handleOnChange} onChange={handleOnChange}
required={false} required={false}
fullWidth={true} fullWidth={true}
/> />
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
</div> </div>
); );
}; };
export default DuplicateDialog; export default DuplicateDialog;

View File

@ -10,7 +10,13 @@ import FormControlLabel from '@mui/material/FormControlLabel';
import Radio from '@mui/material/Radio'; import Radio from '@mui/material/Radio';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import { Designer, TextExporterFactory, ImageExporterFactory, Exporter, Mindmap } from '@wisemapping/mindplot'; import {
Designer,
TextExporterFactory,
ImageExporterFactory,
Exporter,
Mindmap,
} from '@wisemapping/mindplot';
import Client from '../../../../classes/client'; import Client from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice'; import { activeInstance } from '../../../../redux/clientSlice';
@ -22,263 +28,273 @@ type ExportFormat = 'svg' | 'jpg' | 'png' | 'txt' | 'mm' | 'wxml' | 'xls' | 'md'
type ExportGroup = 'image' | 'document' | 'mindmap-tool'; type ExportGroup = 'image' | 'document' | 'mindmap-tool';
type ExportDialogProps = { type ExportDialogProps = {
mapId: number; mapId: number;
enableImgExport: boolean; enableImgExport: boolean;
svgXml?: string; svgXml?: string;
onClose: () => void; onClose: () => void;
}; };
const ExportDialog = ({ const ExportDialog = ({
mapId, mapId,
onClose, onClose,
enableImgExport enableImgExport,
}: ExportDialogProps): React.ReactElement => { }: ExportDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const [submit, setSubmit] = React.useState<boolean>(false); const [submit, setSubmit] = React.useState<boolean>(false);
const { map } = fetchMapById(mapId); const { map } = fetchMapById(mapId);
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const [exportGroup, setExportGroup] = React.useState<ExportGroup>( const [exportGroup, setExportGroup] = React.useState<ExportGroup>(
enableImgExport ? 'image' : 'document' enableImgExport ? 'image' : 'document',
); );
const [exportFormat, setExportFormat] = React.useState<ExportFormat>( const [exportFormat, setExportFormat] = React.useState<ExportFormat>(
enableImgExport ? 'svg' : 'txt' enableImgExport ? 'svg' : 'txt',
); );
const [zoomToFit, setZoomToFit] = React.useState<boolean>(true) const [zoomToFit, setZoomToFit] = React.useState<boolean>(true);
const classes = useStyles(); const classes = useStyles();
const handleOnExportFormatChange = (event) => { const handleOnExportFormatChange = (event) => {
setExportFormat(event.target.value); setExportFormat(event.target.value);
}; };
const handleOnGroupChange = (event) => { const handleOnGroupChange = (event) => {
const value: ExportGroup = event.target.value; const value: ExportGroup = event.target.value;
setExportGroup(value); setExportGroup(value);
let defaultFormat: ExportFormat; let defaultFormat: ExportFormat;
switch (value) { switch (value) {
case 'document': case 'document':
defaultFormat = 'txt'; defaultFormat = 'txt';
break; break;
case 'image': case 'image':
defaultFormat = 'svg'; defaultFormat = 'svg';
break; break;
case 'mindmap-tool': case 'mindmap-tool':
defaultFormat = 'wxml'; defaultFormat = 'wxml';
break; break;
} }
setExportFormat(defaultFormat); setExportFormat(defaultFormat);
}; };
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose(); onClose();
}; };
const handleOnSubmit = (): void => { const handleOnSubmit = (): void => {
setSubmit(true); setSubmit(true);
}; };
const handleOnZoomToFit = (): void => { const handleOnZoomToFit = (): void => {
setZoomToFit(!zoomToFit); setZoomToFit(!zoomToFit);
}; };
const exporter = (formatType: ExportFormat): Promise<string> => { const exporter = (formatType: ExportFormat): Promise<string> => {
let svgElement: Element | null = null; let svgElement: Element | null = null;
let size: SizeType; let size: SizeType;
let mindmap: Mindmap; let mindmap: Mindmap;
const designer: Designer = global.designer; const designer: Designer = global.designer;
if (designer != null) { if (designer != null) {
// Depending on the type of export. It will require differt POST. // Depending on the type of export. It will require differt POST.
const workspace = designer.getWorkSpace(); const workspace = designer.getWorkSpace();
svgElement = workspace.getSVGElement(); svgElement = workspace.getSVGElement();
size = { width: window.innerWidth, height: window.innerHeight }; size = { width: window.innerWidth, height: window.innerHeight };
mindmap = designer.getMindmap(); mindmap = designer.getMindmap();
} else { } else {
mindmap = client.fetchMindmap(mapId); mindmap = client.fetchMindmap(mapId);
} }
let exporter: Exporter; let exporter: Exporter;
switch (formatType) { switch (formatType) {
case 'png': case 'png':
case 'jpg': case 'jpg':
case 'svg': { case 'svg': {
exporter = ImageExporterFactory.create(formatType, svgElement, size.width, size.height, zoomToFit); exporter = ImageExporterFactory.create(
break; formatType,
} svgElement,
case 'wxml': size.width,
case 'mm': size.height,
case 'md': zoomToFit,
case 'txt': { );
exporter = TextExporterFactory.create(formatType, mindmap); break;
break; }
} case 'wxml':
default: case 'mm':
throw new Error('Unsupported encoding'); case 'md':
} case 'txt': {
exporter = TextExporterFactory.create(formatType, mindmap);
break;
}
default:
throw new Error('Unsupported encoding');
}
return exporter.exportAndEncode(); return exporter.exportAndEncode();
}; };
useEffect(() => { useEffect(() => {
if (submit) { if (submit) {
exporter(exportFormat) exporter(exportFormat)
.then((url: string) => { .then((url: string) => {
// Create hidden anchor to force download ... // Create hidden anchor to force download ...
const anchor: HTMLAnchorElement = document.createElement('a'); const anchor: HTMLAnchorElement = document.createElement('a');
anchor.style.display = 'display: none'; anchor.style.display = 'display: none';
anchor.download = `${map?.title}.${exportFormat}`; anchor.download = `${map?.title}.${exportFormat}`;
anchor.href = url; anchor.href = url;
document.body.appendChild(anchor); document.body.appendChild(anchor);
// Trigger click ... // Trigger click ...
anchor.click(); anchor.click();
// Clean up ... // Clean up ...
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
document.body.removeChild(anchor); document.body.removeChild(anchor);
}).catch((fail) => { })
console.error("Unexpected error during export:" + fail); .catch((fail) => {
}); console.error('Unexpected error during export:' + fail);
});
onClose(); onClose();
} }
}, [submit]); }, [submit]);
return ( return (
<div> <div>
<BaseDialog <BaseDialog
onClose={handleOnClose} onClose={handleOnClose}
onSubmit={handleOnSubmit} onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: 'export.title', defaultMessage: 'Export' })} title={intl.formatMessage({ id: 'export.title', defaultMessage: 'Export' })}
description={intl.formatMessage({ id: 'export.desc', defaultMessage: 'Export this map in the format that you want and start using it in your presentations or sharing by email' })} description={intl.formatMessage({
submitButton={intl.formatMessage({ id: 'export.title', defaultMessage: 'Export' })} id: 'export.desc',
> defaultMessage:
{ 'Export this map in the format that you want and start using it in your presentations or sharing by email',
!enableImgExport && })}
<Alert severity="info"> submitButton={intl.formatMessage({ id: 'export.title', defaultMessage: 'Export' })}
<FormattedMessage >
id="export.warning" {!enableImgExport && (
defaultMessage="Exporting to Image (SVG,PNG,JPEG,PDF) is only available in the editor toolbar." <Alert severity="info">
/> <FormattedMessage
</Alert> id="export.warning"
} defaultMessage="Exporting to Image (SVG,PNG,JPEG,PDF) is only available in the editor toolbar."
<FormControl component="fieldset"> />
<RadioGroup name="export" value={exportGroup} onChange={handleOnGroupChange}> </Alert>
<FormControl> )}
<FormControlLabel <FormControl component="fieldset">
className={classes.label} <RadioGroup name="export" value={exportGroup} onChange={handleOnGroupChange}>
value="image" <FormControl>
disabled={!enableImgExport} <FormControlLabel
control={<Radio color="primary" />} className={classes.label}
label={intl.formatMessage({ value="image"
id: 'export.image', disabled={!enableImgExport}
defaultMessage: control={<Radio color="primary" />}
'Image: Get a graphic representation of your map including all colors and shapes.', label={intl.formatMessage({
})} id: 'export.image',
color="secondary" defaultMessage:
style={{ fontSize: '9px' }} 'Image: Get a graphic representation of your map including all colors and shapes.',
/> })}
{exportGroup == 'image' && ( color="secondary"
<> style={{ fontSize: '9px' }}
<Select />
onChange={handleOnExportFormatChange} {exportGroup == 'image' && (
variant="outlined" <>
value={exportFormat} <Select
className={classes.select} onChange={handleOnExportFormatChange}
> variant="outlined"
<MenuItem value="svg" className={classes.menu}> value={exportFormat}
Scalable Vector Graphics (SVG) className={classes.select}
</MenuItem> >
<MenuItem value="png" className={classes.menu}> <MenuItem value="svg" className={classes.menu}>
Portable Network Graphics (PNG) Scalable Vector Graphics (SVG)
</MenuItem> </MenuItem>
<MenuItem value="jpg" className={classes.menu}> <MenuItem value="png" className={classes.menu}>
JPEG Image (JPEG) Portable Network Graphics (PNG)
</MenuItem> </MenuItem>
</Select> <MenuItem value="jpg" className={classes.menu}>
<FormControlLabel JPEG Image (JPEG)
className={classes.select} </MenuItem>
control={<Checkbox checked={zoomToFit} onChange={handleOnZoomToFit} />} </Select>
label={intl.formatMessage({ <FormControlLabel
id: 'export.img-center', className={classes.select}
defaultMessage: control={<Checkbox checked={zoomToFit} onChange={handleOnZoomToFit} />}
'Center and zoom to fit', label={intl.formatMessage({
})} /> id: 'export.img-center',
</> defaultMessage: 'Center and zoom to fit',
)} })}
</FormControl> />
</>
)}
</FormControl>
<FormControl> <FormControl>
<FormControlLabel <FormControlLabel
className={classes.label} className={classes.label}
value="document" value="document"
control={<Radio color="primary" />} control={<Radio color="primary" />}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'export.document-label', id: 'export.document-label',
defaultMessage: defaultMessage:
'Document: Export your mindmap in a self-contained document ready to share', 'Document: Export your mindmap in a self-contained document ready to share',
})} })}
color="secondary" color="secondary"
/> />
{exportGroup == 'document' && ( {exportGroup == 'document' && (
<Select <Select
onChange={handleOnExportFormatChange} onChange={handleOnExportFormatChange}
variant="outlined" variant="outlined"
value={exportFormat} value={exportFormat}
className={classes.select} className={classes.select}
> >
<MenuItem className={classes.select} value="txt"> <MenuItem className={classes.select} value="txt">
Plain Text File (TXT) Plain Text File (TXT)
</MenuItem> </MenuItem>
<MenuItem className={classes.select} value="md"> <MenuItem className={classes.select} value="md">
Markdown (MD) Markdown (MD)
</MenuItem> </MenuItem>
{/* <MenuItem className={classes.select} value="xls"> {/* <MenuItem className={classes.select} value="xls">
Microsoft Excel (XLS) Microsoft Excel (XLS)
</MenuItem> */} </MenuItem> */}
</Select> </Select>
)} )}
</FormControl> </FormControl>
<FormControl> <FormControl>
<FormControlLabel <FormControlLabel
className={classes.label} className={classes.label}
value="mindmap-tool" value="mindmap-tool"
control={<Radio color="primary" />} control={<Radio color="primary" />}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'export.document', id: 'export.document',
defaultMessage: defaultMessage:
'Mindmap Tools: Export your mindmap in thirdparty mindmap tool formats', 'Mindmap Tools: Export your mindmap in thirdparty mindmap tool formats',
})} })}
color="secondary" color="secondary"
/> />
{exportGroup == 'mindmap-tool' && ( {exportGroup == 'mindmap-tool' && (
<Select <Select
onChange={handleOnExportFormatChange} onChange={handleOnExportFormatChange}
variant="outlined" variant="outlined"
className={classes.select} className={classes.select}
value={exportFormat} value={exportFormat}
> >
<MenuItem className={classes.select} value="wxml"> <MenuItem className={classes.select} value="wxml">
WiseMapping (WXML) WiseMapping (WXML)
</MenuItem> </MenuItem>
<MenuItem className={classes.select} value="mm"> <MenuItem className={classes.select} value="mm">
Freemind 1.0.1 (MM) Freemind 1.0.1 (MM)
</MenuItem> </MenuItem>
{/* <MenuItem className={classes.select} value="mmap"> {/* <MenuItem className={classes.select} value="mmap">
MindManager (MMAP) MindManager (MMAP)
</MenuItem> */} </MenuItem> */}
</Select> </Select>
)} )}
</FormControl> </FormControl>
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
</div> </div>
); );
}; };
export default ExportDialog; export default ExportDialog;

View File

@ -19,115 +19,94 @@ import Link from '@mui/material/Link';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
const HistoryDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const HistoryDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const { data } = useQuery<unknown, ErrorInfo, ChangeHistory[]>(`history-${mapId}`, () => { const { data } = useQuery<unknown, ErrorInfo, ChangeHistory[]>(`history-${mapId}`, () => {
return client.fetchHistory(mapId); return client.fetchHistory(mapId);
});
const changeHistory: ChangeHistory[] = data ? data : [];
const handleOnClose = (): void => {
queryClient.invalidateQueries(`history-${mapId}`);
onClose();
};
const handleOnClick = (event, vid: number): void => {
event.preventDefault();
client.revertHistory(mapId, vid).then(() => {
handleOnClose();
}); });
const changeHistory: ChangeHistory[] = data ? data : []; };
const handleOnClose = (): void => { return (
queryClient.invalidateQueries(`history-${mapId}`); <div>
onClose(); <BaseDialog
}; onClose={handleOnClose}
title={intl.formatMessage({
const handleOnClick = (event, vid: number): void => { id: 'action.history-title',
event.preventDefault(); defaultMessage: 'Version history',
client.revertHistory(mapId, vid).then(() => { })}
handleOnClose(); description={intl.formatMessage({
}); id: 'action.history-description',
}; defaultMessage: 'List of changes introduced in the last 90 days.',
})}
return ( >
<div> <TableContainer component={Paper} style={{ maxHeight: '200px' }} variant="outlined">
<BaseDialog <Table size="small" stickyHeader>
onClose={handleOnClose} <TableHead>
title={intl.formatMessage({ <TableRow>
id: 'action.history-title', <TableCell align="left">
defaultMessage: 'Version history', <FormattedMessage id="maps.modified-by" defaultMessage="Modified By" />
})} </TableCell>
description={intl.formatMessage({ <TableCell align="left">
id: 'action.history-description', <FormattedMessage id="maps.modified" defaultMessage="Modified" />
defaultMessage: 'List of changes introduced in the last 90 days.', </TableCell>
})} <TableCell align="left"></TableCell>
> <TableCell align="left"></TableCell>
<TableContainer component={Paper} style={{ maxHeight: '200px' }} variant="outlined"> </TableRow>
<Table size="small" stickyHeader> </TableHead>
<TableHead> <TableBody>
<TableRow> {changeHistory.length == 0 ? (
<TableCell align="left"> <TableRow>
<FormattedMessage <TableCell colSpan={4}>
id="maps.modified-by" <FormattedMessage
defaultMessage="Modified By" id="history.no-changes"
/> defaultMessage="There is no changes available"
</TableCell> />
<TableCell align="left"> </TableCell>
<FormattedMessage </TableRow>
id="maps.modified" ) : (
defaultMessage="Modified" changeHistory.map((row) => (
/> <TableRow key={row.id}>
</TableCell> <TableCell align="left">{row.lastModificationBy}</TableCell>
<TableCell align="left"></TableCell> <TableCell align="left">
<TableCell align="left"></TableCell> <Tooltip
</TableRow> title={dayjs(row.lastModificationTime).format('lll')}
</TableHead> placement="bottom-start"
<TableBody> >
{changeHistory.length == 0 ? ( <span>{dayjs(row.lastModificationTime).fromNow()}</span>
<TableRow> </Tooltip>
<TableCell colSpan={4}> </TableCell>
<FormattedMessage <TableCell align="left">
id="history.no-changes" <Link href={`/c/maps/${mapId}/${row.id}/view`} target="history">
defaultMessage="There is no changes available" <FormattedMessage id="maps.view" defaultMessage="View" />
/> </Link>
</TableCell> </TableCell>
</TableRow> <TableCell align="left">
) : ( <Link href="#" onClick={(e) => handleOnClick(e, row.id)}>
changeHistory.map((row) => ( <FormattedMessage id="maps.revert" defaultMessage="Revert" />
<TableRow key={row.id}> </Link>
<TableCell align="left">{row.lastModificationBy}</TableCell> </TableCell>
<TableCell align="left"> </TableRow>
<Tooltip ))
title={dayjs(row.lastModificationTime).format( )}
'lll' </TableBody>
)} </Table>
placement="bottom-start" </TableContainer>
> </BaseDialog>
<span> </div>
{dayjs(row.lastModificationTime).fromNow()} );
</span>
</Tooltip>
</TableCell>
<TableCell align="left">
<Link
href={`/c/maps/${mapId}/${row.id}/view`}
target="history"
>
<FormattedMessage
id="maps.view"
defaultMessage="View"
/>
</Link>
</TableCell>
<TableCell align="left">
<Link
href="#"
onClick={(e) => handleOnClick(e, row.id)}>
<FormattedMessage
id="maps.revert"
defaultMessage="Revert"
/>
</Link>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
</BaseDialog>
</div>
);
}; };
export default HistoryDialog; export default HistoryDialog;

View File

@ -13,204 +13,206 @@ import Input from '../../../form/input';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';
export type ImportModel = { export type ImportModel = {
title: string; title: string;
description?: string; description?: string;
contentType?: string; contentType?: string;
content?: null | string; content?: null | string;
}; };
export type CreateProps = { export type CreateProps = {
onClose: () => void; onClose: () => void;
}; };
type ErrorFile = { type ErrorFile = {
error: boolean; error: boolean;
message: string; message: string;
} };
const defaultModel: ImportModel = { title: '' }; const defaultModel: ImportModel = { title: '' };
const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => { const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => {
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ImportModel>(defaultModel); const [model, setModel] = React.useState<ImportModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const [errorFile, setErrorFile] = React.useState<ErrorFile>({error: false, message: ''}); const [errorFile, setErrorFile] = React.useState<ErrorFile>({ error: false, message: '' });
const intl = useIntl(); const intl = useIntl();
const mutation = useMutation<number, ErrorInfo, ImportModel>( const mutation = useMutation<number, ErrorInfo, ImportModel>(
(model: ImportModel) => { (model: ImportModel) => {
return client.importMap(model); return client.importMap(model);
}, },
{ {
onSuccess: (mapId: number) => { onSuccess: (mapId: number) => {
window.location.href = `/c/maps/${mapId}/edit`; window.location.href = `/c/maps/${mapId}/edit`;
}, },
onError: (error) => { onError: (error) => {
setError(error); setError(error);
}, },
},
);
const handleOnClose = (): void => {
onClose();
setModel(defaultModel);
setError(undefined);
};
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
mutation.mutate(model);
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ImportModel]: value });
};
const handleOnFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event?.target?.files;
const reader = new FileReader();
if (files) {
const file = files[0];
// Closure to capture the file information.
reader.onload = (event) => {
// Suggest file name ...
const fileName = file.name;
if (fileName) {
const title = fileName.split('.')[0];
if (!model.title || 0 === model.title.length) {
model.title = title;
}
} }
);
const handleOnClose = (): void => { const extensionFile = file.name.split('.').pop();
onClose(); const extensionAccept = ['wxml', 'mm'];
setModel(defaultModel);
setError(undefined);
};
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { if (!extensionAccept.includes(extensionFile)) {
event.preventDefault(); setErrorFile({
mutation.mutate(model); error: true,
}; message: intl.formatMessage(
{
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { id: 'import.error-file',
event.preventDefault(); defaultMessage: 'Import error {error}',
},
const name = event.target.name; {
const value = event.target.value; error:
setModel({ ...model, [name as keyof ImportModel]: value }); 'You can import WiseMapping and Freemind maps to your list of maps. Select the file you want to import.',
}; },
),
const handleOnFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { });
const files = event?.target?.files;
const reader = new FileReader();
if (files) {
const file = files[0];
// Closure to capture the file information.
reader.onload = (event) => {
// Suggest file name ...
const fileName = file.name;
if (fileName) {
const title = fileName.split('.')[0];
if (!model.title || 0 === model.title.length) {
model.title = title;
}
}
const extensionFile = file.name.split('.').pop();
const extensionAccept = ['wxml', 'mm'];
if (!extensionAccept.includes(extensionFile)) {
setErrorFile({
error: true,
message: intl.formatMessage({
id: 'import.error-file',
defaultMessage: 'Import error {error}',
},
{
error: 'You can import WiseMapping and Freemind maps to your list of maps. Select the file you want to import.'
})
});
}
model.contentType = 'application/xml'
const fileContent = event?.target?.result;
const mapConent: string = typeof fileContent === 'string' ? fileContent : fileContent.toString();
try {
const importer: Importer = TextImporterFactory.create(extensionFile, mapConent)
importer.import(model.title, model.description)
.then(res => {
model.content = res;
setModel({ ...model });
})
} catch (e) {
if (e instanceof Error) {
setErrorFile({
error: true,
message: intl.formatMessage({
id: 'import.error-file',
defaultMessage: 'Import error {error}',
},
{
error: e.message
})
});
}
}
};
// Read in the image file as a data URL.
reader.readAsText(file);
} }
};
return ( model.contentType = 'application/xml';
<div>
<BaseDialog const fileContent = event?.target?.result;
onClose={handleOnClose} const mapConent: string =
onSubmit={handleOnSubmit} typeof fileContent === 'string' ? fileContent : fileContent.toString();
error={error}
title={intl.formatMessage({ try {
id: 'import.title', const importer: Importer = TextImporterFactory.create(extensionFile, mapConent);
defaultMessage: 'Import existing mindmap',
})} importer.import(model.title, model.description).then((res) => {
description={intl.formatMessage({ model.content = res;
id: 'import.description', setModel({ ...model });
defaultMessage: });
'You can import WiseMapping and Freemind maps to your list of maps. Select the file you want to import.', } catch (e) {
})} if (e instanceof Error) {
submitButton={intl.formatMessage({ id: 'import.button', defaultMessage: 'Create' })} setErrorFile({
error: true,
message: intl.formatMessage(
{
id: 'import.error-file',
defaultMessage: 'Import error {error}',
},
{
error: e.message,
},
),
});
}
}
};
// Read in the image file as a data URL.
reader.readAsText(file);
}
};
return (
<div>
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({
id: 'import.title',
defaultMessage: 'Import existing mindmap',
})}
description={intl.formatMessage({
id: 'import.description',
defaultMessage:
'You can import WiseMapping and Freemind maps to your list of maps. Select the file you want to import.',
})}
submitButton={intl.formatMessage({ id: 'import.button', defaultMessage: 'Create' })}
>
{errorFile.error && (
<Alert severity="error">
<p>{errorFile.message}</p>
</Alert>
)}
<FormControl fullWidth={true}>
<input
accept=".wxml,.mm"
id="contained-button-file"
type="file"
required={true}
style={{ display: 'none' }}
onChange={handleOnFileChange}
/>
<Input
name="title"
type="text"
label={intl.formatMessage({
id: 'action.rename-name-placeholder',
defaultMessage: 'Name',
})}
value={model.title}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input
name="description"
type="text"
label={intl.formatMessage({
id: 'action.rename-description-placeholder',
defaultMessage: 'Description',
})}
value={model.description}
onChange={handleOnChange}
required={false}
fullWidth={true}
/>
<label htmlFor="contained-button-file">
<Button
variant="outlined"
color="primary"
component="span"
style={{ margin: '10px 5px', width: '100%' }}
> >
{errorFile.error && <FormattedMessage id="maps.choose-file" defaultMessage="Choose a file" />
<Alert severity='error'> </Button>
<p>{errorFile.message}</p> </label>
</Alert> </FormControl>
} </BaseDialog>
<FormControl fullWidth={true}> </div>
<input );
accept=".wxml,.mm"
id="contained-button-file"
type="file"
required={true}
style={{ display: 'none' }}
onChange={handleOnFileChange}
/>
<Input
name="title"
type="text"
label={intl.formatMessage({
id: 'action.rename-name-placeholder',
defaultMessage: 'Name',
})}
value={model.title}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input
name="description"
type="text"
label={intl.formatMessage({
id: 'action.rename-description-placeholder',
defaultMessage: 'Description',
})}
value={model.description}
onChange={handleOnChange}
required={false}
fullWidth={true}
/>
<label htmlFor="contained-button-file">
<Button
variant="outlined"
color="primary"
component="span"
style={{ margin: '10px 5px', width: '100%' }}
>
<FormattedMessage
id="maps.choose-file"
defaultMessage="Choose a file"
/>
</Button>
</label>
</FormControl>
</BaseDialog>
</div>
);
}; };
export default ImportDialog; export default ImportDialog;

View File

@ -15,87 +15,87 @@ import ShareDialog from './share-dialog';
import LabelDialog from './label-dialog'; import LabelDialog from './label-dialog';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
export type BasicMapInfo = { export type BasicMapInfo = {
name: string; name: string;
description: string | undefined; description: string | undefined;
}; };
type ActionDialogProps = { type ActionDialogProps = {
action?: ActionType; action?: ActionType;
mapsId: number[]; mapsId: number[];
onClose: (success?: boolean) => void; onClose: (success?: boolean) => void;
fromEditor: boolean; fromEditor: boolean;
}; };
const ActionDispatcher = ({ mapsId, action, onClose, fromEditor }: ActionDialogProps): React.ReactElement => { const ActionDispatcher = ({
mapsId,
action,
onClose,
fromEditor,
}: ActionDialogProps): React.ReactElement => {
useEffect(() => {
ReactGA.event({
category: 'map metadata',
action: action,
nonInteraction: true,
});
}, [action]);
useEffect(() => { const handleOnClose = (success?: boolean): void => {
ReactGA.event({ onClose(success);
category: 'map metadata', };
action: action,
nonInteraction: true
}); switch (action) {
}, [action]); case 'open':
window.location.href = `/c/maps/${mapsId}/edit`;
break;
case 'print':
window.open(`/c/maps/${mapsId}/print`, 'print');
break;
}
const handleOnClose = (success?: boolean): void => { return (
onClose(success); <span>
}; {action === 'create' && <CreateDialog onClose={handleOnClose} />}
{action === 'delete' && mapsId.length == 1 && (
switch (action) { <DeleteDialog onClose={handleOnClose} mapId={mapsId[0]} />
case 'open': )}
window.location.href = `/c/maps/${mapsId}/edit`; {action === 'delete' && mapsId.length > 1 && (
break; <DeleteMultiselectDialog onClose={handleOnClose} mapsId={mapsId} />
case 'print': )}
window.open(`/c/maps/${mapsId}/print`, 'print'); {action === 'rename' && <RenameDialog onClose={handleOnClose} mapId={mapsId[0]} />}
break; {action === 'duplicate' && <DuplicateDialog onClose={handleOnClose} mapId={mapsId[0]} />}
} {action === 'history' && <HistoryDialog onClose={handleOnClose} mapId={mapsId[0]} />}
{action === 'import' && <ImportDialog onClose={handleOnClose} />}
return ( {action === 'publish' && <PublishDialog onClose={handleOnClose} mapId={mapsId[0]} />}
<span> {action === 'info' && <InfoDialog onClose={handleOnClose} mapId={mapsId[0]} />}
{action === 'create' && <CreateDialog onClose={handleOnClose} />} {action === 'create' && <CreateDialog onClose={handleOnClose} />}
{action === 'delete' && mapsId.length == 1 && ( {action === 'export' && (
<DeleteDialog onClose={handleOnClose} mapId={mapsId[0]} /> <ExportDialog onClose={handleOnClose} mapId={mapsId[0]} enableImgExport={fromEditor} />
)} )}
{action === 'delete' && mapsId.length > 1 && ( {action === 'share' && <ShareDialog onClose={handleOnClose} mapId={mapsId[0]} />}
<DeleteMultiselectDialog onClose={handleOnClose} mapsId={mapsId} /> {action === 'label' && <LabelDialog onClose={handleOnClose} mapsId={mapsId} />}
)} </span>
{action === 'rename' && <RenameDialog onClose={handleOnClose} mapId={mapsId[0]} />} );
{action === 'duplicate' && (
<DuplicateDialog onClose={handleOnClose} mapId={mapsId[0]} />
)}
{action === 'history' && <HistoryDialog onClose={handleOnClose} mapId={mapsId[0]} />}
{action === 'import' && <ImportDialog onClose={handleOnClose} />}
{action === 'publish' && <PublishDialog onClose={handleOnClose} mapId={mapsId[0]} />}
{action === 'info' && <InfoDialog onClose={handleOnClose} mapId={mapsId[0]} />}
{action === 'create' && <CreateDialog onClose={handleOnClose} />}
{action === 'export' && (
<ExportDialog onClose={handleOnClose} mapId={mapsId[0]} enableImgExport={fromEditor} />
)}
{action === 'share' && <ShareDialog onClose={handleOnClose} mapId={mapsId[0]} />}
{action === 'label' && <LabelDialog onClose={handleOnClose} mapsId={mapsId} />}
</span>
);
}; };
ActionDispatcher.defaultProps = { ActionDispatcher.defaultProps = {
fromEditor: false, fromEditor: false,
}; };
export const handleOnMutationSuccess = (onClose: () => void, queryClient: QueryClient): void => { export const handleOnMutationSuccess = (onClose: () => void, queryClient: QueryClient): void => {
queryClient.invalidateQueries('maps'); queryClient.invalidateQueries('maps');
onClose(); onClose();
}; };
export type SimpleDialogProps = { export type SimpleDialogProps = {
mapId: number; mapId: number;
onClose: (success?: boolean) => void; onClose: (success?: boolean) => void;
}; };
export type MultiDialogProps = { export type MultiDialogProps = {
mapsId: number[]; mapsId: number[];
onClose: (success?: boolean) => void; onClose: (success?: boolean) => void;
}; };
export default ActionDispatcher; export default ActionDispatcher;

View File

@ -14,174 +14,112 @@ import Typography from '@mui/material/Typography';
import List from '@mui/material/List'; import List from '@mui/material/List';
import LocalizedFormat from 'dayjs/plugin/localizedFormat'; import LocalizedFormat from 'dayjs/plugin/localizedFormat';
// Load fromNow pluggin // Load fromNow pluggin
dayjs.extend(LocalizedFormat) dayjs.extend(LocalizedFormat);
const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = fetchMapById(mapId); const { map } = fetchMapById(mapId);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl(); const intl = useIntl();
const classes = useStyles(); const classes = useStyles();
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose(); onClose();
setError(undefined); setError(undefined);
}; };
return ( return (
<BaseDialog <BaseDialog
onClose={handleOnClose} onClose={handleOnClose}
error={error} error={error}
title={intl.formatMessage({ id: 'info.title', defaultMessage: 'Info' })} title={intl.formatMessage({ id: 'info.title', defaultMessage: 'Info' })}
description={intl.formatMessage({ description={intl.formatMessage({
id: 'info.description-msg', id: 'info.description-msg',
defaultMessage: defaultMessage: 'By publishing the map you make it visible to everyone on the Internet.',
'By publishing the map you make it visible to everyone on the Internet.', })}
})} submitButton={intl.formatMessage({ id: 'info.button', defaultMessage: 'Accept' })}
submitButton={intl.formatMessage({ id: 'info.button', defaultMessage: 'Accept' })} >
> <Paper style={{ maxHeight: 200, overflowY: 'scroll' }} variant="outlined" elevation={0}>
<Paper style={{ maxHeight: 200, overflowY: 'scroll' }} variant="outlined" elevation={0}> <Card variant="outlined">
<Card variant="outlined"> <List dense={true}>
<List dense={true}> <ListItem>
<ListItem> <Typography variant="body1" style={{ fontWeight: 'bold' }}>
<Typography variant="body1" style={{ fontWeight: 'bold' }}> <FormattedMessage id="info.basic-info" defaultMessage="Basic Info" />
<FormattedMessage </Typography>
id="info.basic-info" </ListItem>
defaultMessage="Basic Info"
/>
</Typography>
</ListItem>
<ListItem> <ListItem>
<Typography <Typography variant="caption" color="textPrimary" className={classes.textDesc}>
variant="caption" <FormattedMessage id="info.name" defaultMessage="Name" />:
color="textPrimary" </Typography>
className={classes.textDesc} <Typography variant="body2">{map?.title}</Typography>
> </ListItem>
<FormattedMessage id="info.name" defaultMessage="Name" />:
</Typography>
<Typography variant="body2">{map?.title}</Typography>
</ListItem>
<ListItem> <ListItem>
<Typography <Typography variant="caption" color="textPrimary" className={classes.textDesc}>
variant="caption" <FormattedMessage id="info.description" defaultMessage="Description" />:
color="textPrimary" </Typography>
className={classes.textDesc} <Typography variant="body2">{map?.description}</Typography>
> </ListItem>
<FormattedMessage
id="info.description"
defaultMessage="Description"
/>
:
</Typography>
<Typography variant="body2">{map?.description}</Typography>
</ListItem>
<ListItem> <ListItem>
<Typography <Typography variant="caption" color="textPrimary" className={classes.textDesc}>
variant="caption" <FormattedMessage id="info.creator" defaultMessage="Creator" />:
color="textPrimary" </Typography>
className={classes.textDesc} <Typography variant="body2">{map?.createdBy}</Typography>
> </ListItem>
<FormattedMessage id="info.creator" defaultMessage="Creator" />:
</Typography>
<Typography variant="body2">{map?.createdBy}</Typography>
</ListItem>
<ListItem> <ListItem>
<Typography <Typography variant="caption" color="textPrimary" className={classes.textDesc}>
variant="caption" <FormattedMessage id="info.creation-time" defaultMessage="Creation Date" />:
color="textPrimary" </Typography>
className={classes.textDesc} <Typography variant="body2">{dayjs(map?.creationTime).format('LLL')}</Typography>
> </ListItem>
<FormattedMessage
id="info.creation-time"
defaultMessage="Creation Date"
/>
:
</Typography>
<Typography variant="body2">
{dayjs(map?.creationTime).format('LLL')}
</Typography>
</ListItem>
<ListItem> <ListItem>
<Typography <Typography variant="caption" color="textPrimary" className={classes.textDesc}>
variant="caption" <FormattedMessage id="info.modified-tny" defaultMessage="Last Modified By" />:
color="textPrimary" </Typography>
className={classes.textDesc} <Typography variant="body2">{map?.lastModificationBy}</Typography>
> </ListItem>
<FormattedMessage
id="info.modified-tny"
defaultMessage="Last Modified By"
/>
:
</Typography>
<Typography variant="body2">{map?.lastModificationBy}</Typography>
</ListItem>
<ListItem> <ListItem>
<Typography <Typography variant="caption" color="textPrimary" className={classes.textDesc}>
variant="caption" <FormattedMessage id="info.modified-time" defaultMessage="Last Modified Date" />:
color="textPrimary" </Typography>
className={classes.textDesc} <Typography variant="body2">
> {dayjs(map?.lastModificationTime).format('LLL')}
<FormattedMessage </Typography>
id="info.modified-time" </ListItem>
defaultMessage="Last Modified Date"
/>
:
</Typography>
<Typography variant="body2">
{dayjs(map?.lastModificationTime).format('LLL')}
</Typography>
</ListItem>
<ListItem> <ListItem>
<Typography <Typography variant="caption" color="textPrimary" className={classes.textDesc}>
variant="caption" <FormattedMessage id="info.starred" defaultMessage="Starred" />:
color="textPrimary" </Typography>
className={classes.textDesc} <Typography variant="body2">{Boolean(map?.starred).toString()}</Typography>
> </ListItem>
<FormattedMessage id="info.starred" defaultMessage="Starred" />: </List>
</Typography> </Card>
<Typography variant="body2">
{Boolean(map?.starred).toString()}
</Typography>
</ListItem>
</List>
</Card>
<Card variant="outlined" style={{ marginTop: '10px' }}> <Card variant="outlined" style={{ marginTop: '10px' }}>
<List dense={true}> <List dense={true}>
<ListItem> <ListItem>
<Typography variant="body1" style={{ fontWeight: 'bold' }}> <Typography variant="body1" style={{ fontWeight: 'bold' }}>
<FormattedMessage id="info.sharing" defaultMessage="Sharing" /> <FormattedMessage id="info.sharing" defaultMessage="Sharing" />
</Typography> </Typography>
</ListItem> </ListItem>
</List> </List>
<ListItem> <ListItem>
<Typography <Typography variant="caption" color="textPrimary" className={classes.textDesc}>
variant="caption" <FormattedMessage id="info.public-visibility" defaultMessage="Publicly Visible" />:
color="textPrimary" </Typography>
className={classes.textDesc} <Typography variant="body2">{Boolean(map?.isPublic).toString()}</Typography>
> </ListItem>
<FormattedMessage </Card>
id="info.public-visibility" </Paper>
defaultMessage="Publicly Visible" </BaseDialog>
/> );
:
</Typography>
<Typography variant="body2">{Boolean(map?.isPublic).toString()}</Typography>
</ListItem>
</Card>
</Paper>
</BaseDialog>
);
}; };
export default InfoDialog; export default InfoDialog;

View File

@ -12,75 +12,77 @@ import { LabelSelector } from '../../maps-list/label-selector';
import { activeInstance } from '../../../../redux/clientSlice'; import { activeInstance } from '../../../../redux/clientSlice';
import { ChangeLabelMutationFunctionParam, getChangeLabelMutationFunction } from '../../maps-list'; import { ChangeLabelMutationFunctionParam, getChangeLabelMutationFunction } from '../../maps-list';
const LabelDialog = ({ mapsId, onClose }: MultiDialogProps): React.ReactElement => { const LabelDialog = ({ mapsId, onClose }: MultiDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const classes = useStyles(); const classes = useStyles();
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
// TODO: pass down map data instead of using query? // TODO: pass down map data instead of using query?
const { data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => { const { data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
return client.fetchAllMaps(); return client.fetchAllMaps();
});
const [error, setError] = React.useState<ErrorInfo>();
const maps = data.filter((m) => mapsId.includes(m.id));
const changeLabelMutation = useMutation<
void,
ErrorInfo,
ChangeLabelMutationFunctionParam,
number
>(getChangeLabelMutationFunction(client), {
onSuccess: () => {
queryClient.invalidateQueries('maps');
queryClient.invalidateQueries('labels');
},
onError: (error) => {
setError(error);
},
});
const handleChangesInLabels = (label: Label, checked: boolean) => {
setError(undefined);
changeLabelMutation.mutate({
maps,
label,
checked,
}); });
const [error, setError] = React.useState<ErrorInfo>(); };
const maps = data.filter(m => mapsId.includes(m.id)); return (
<div>
const changeLabelMutation = useMutation<void, ErrorInfo, ChangeLabelMutationFunctionParam, number>( <BaseDialog
getChangeLabelMutationFunction(client), onClose={onClose}
{ title={intl.formatMessage({
onSuccess: () => { id: 'label.title',
queryClient.invalidateQueries('maps'); defaultMessage: 'Add a label',
queryClient.invalidateQueries('labels'); })}
}, description={intl.formatMessage({
onError: (error) => { id: 'label.description',
setError(error); defaultMessage: 'Use labels to organize your maps.',
} })}
} PaperProps={{ classes: { root: classes.paper } }}
); error={error}
>
const handleChangesInLabels = (label: Label, checked: boolean) => { <>
setError(undefined); <Typography variant="body2" marginTop="10px">
changeLabelMutation.mutate({ <FormattedMessage id="label.add-for" defaultMessage="Editing labels for " />
maps, {maps.length > 1 ? (
label, <FormattedMessage
checked id="label.maps-count"
}); defaultMessage="{count} maps"
}; values={{ count: maps.length }}
/>
return ( ) : (
<div> maps.map((m) => m.title).join(', ')
<BaseDialog )}
onClose={onClose} </Typography>
title={intl.formatMessage({ <LabelSelector onChange={handleChangesInLabels} maps={maps} />
id: 'label.title', </>
defaultMessage: 'Add a label', </BaseDialog>
})} </div>
description={intl.formatMessage({ );
id: 'label.description',
defaultMessage:
'Use labels to organize your maps.',
})}
PaperProps={{ classes: { root: classes.paper } }}
error={error}
>
<>
<Typography variant="body2" marginTop="10px">
<FormattedMessage id="label.add-for" defaultMessage="Editing labels for " />
{
maps.length > 1 ?
<FormattedMessage id="label.maps-count"
defaultMessage="{count} maps"
values={{ count: maps.length }}
/> :
maps.map(m => m.title).join(', ')
}
</Typography>
<LabelSelector onChange={handleChangesInLabels} maps={maps} />
</>
</BaseDialog>
</div>);
}; };
export default LabelDialog; export default LabelDialog;

View File

@ -21,139 +21,133 @@ import Box from '@mui/system/Box';
import AppConfig from '../../../../classes/app-config'; import AppConfig from '../../../../classes/app-config';
const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = fetchMapById(mapId); const { map } = fetchMapById(mapId);
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false); const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const [activeTab, setActiveTab] = React.useState('1'); const [activeTab, setActiveTab] = React.useState('1');
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const intl = useIntl(); const intl = useIntl();
const classes = useStyles(); const classes = useStyles();
const mutation = useMutation<void, ErrorInfo, boolean>( const mutation = useMutation<void, ErrorInfo, boolean>(
(model: boolean) => { (model: boolean) => {
return client.updateMapToPublic(mapId, model); return client.updateMapToPublic(mapId, model);
}, },
{ {
onSuccess: () => { onSuccess: () => {
setModel(model); setModel(model);
handleOnMutationSuccess(onClose, queryClient); handleOnMutationSuccess(onClose, queryClient);
}, },
onError: (error) => { onError: (error) => {
setError(error); setError(error);
}, },
} },
); );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose(); onClose();
setError(undefined); setError(undefined);
}; };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault(); event.preventDefault();
mutation.mutate(model); mutation.mutate(model);
}; };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean): void => {
event.preventDefault(); event.preventDefault();
setModel(checked); setModel(checked);
}; };
const handleTabChange = (event: React.ChangeEvent<HTMLInputElement>, newValue: string) => { const handleTabChange = (event: React.ChangeEvent<HTMLInputElement>, newValue: string) => {
setActiveTab(newValue); setActiveTab(newValue);
}; };
const baseUrl = AppConfig.getBaseUrl(); const baseUrl = AppConfig.getBaseUrl();
return ( return (
<div> <div>
<BaseDialog <BaseDialog
onClose={handleOnClose} onClose={handleOnClose}
onSubmit={handleOnSubmit} onSubmit={handleOnSubmit}
error={error} error={error}
title={intl.formatMessage({ id: 'publish.title', defaultMessage: 'Publish' })} title={intl.formatMessage({ id: 'publish.title', defaultMessage: 'Publish' })}
description={intl.formatMessage({ description={intl.formatMessage({
id: 'publish.description', id: 'publish.description',
defaultMessage: defaultMessage: 'By publishing the map you make it visible to everyone on the Internet.',
'By publishing the map you make it visible to everyone on the Internet.', })}
})} submitButton={intl.formatMessage({
submitButton={intl.formatMessage({ id: 'publish.button',
id: 'publish.button', defaultMessage: 'Accept',
defaultMessage: 'Accept', })}
})} >
> <FormControl fullWidth={true}>
<FormControl fullWidth={true}> <FormControlLabel
<FormControlLabel control={
control={ <Checkbox checked={model} onChange={handleOnChange} name="public" color="primary" />
<Checkbox }
checked={model} label={intl.formatMessage({
onChange={handleOnChange} id: 'publish.checkbox',
name="public" defaultMessage: 'Enable public sharing',
color="primary" })}
/> />
} </FormControl>
label={intl.formatMessage({
id: 'publish.checkbox',
defaultMessage: 'Enable public sharing',
})}
/>
</FormControl>
<div style={!model ? { visibility: 'hidden' } : {}}> <div style={!model ? { visibility: 'hidden' } : {}}>
<TabContext value={activeTab}> <TabContext value={activeTab}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}> <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={handleTabChange}> <TabList onChange={handleTabChange}>
<Tab <Tab
label={intl.formatMessage({ label={intl.formatMessage({
id: 'publish.public-url', id: 'publish.public-url',
defaultMessage: 'Public URL', defaultMessage: 'Public URL',
})} })}
value="1" value="1"
/> />
<Tab <Tab
label={intl.formatMessage({ label={intl.formatMessage({
id: 'publish.embedded', id: 'publish.embedded',
defaultMessage: 'Embedded', defaultMessage: 'Embedded',
})} })}
value="2" value="2"
/> />
</TabList> </TabList>
</Box> </Box>
<TabPanel value="2"> <TabPanel value="2">
<Typography variant="subtitle2"> <Typography variant="subtitle2">
<FormattedMessage <FormattedMessage
id="publish.embedded-msg" id="publish.embedded-msg"
defaultMessage="Copy this snippet of code to embed in your blog or page:" defaultMessage="Copy this snippet of code to embed in your blog or page:"
/> />
</Typography> </Typography>
<TextareaAutosize <TextareaAutosize
className={classes.textarea} className={classes.textarea}
readOnly={true} readOnly={true}
spellCheck={false} spellCheck={false}
maxRows={6} maxRows={6}
defaultValue={`<iframe style="width:600px;height:400px;border:1px solid black" src="${baseUrl}/c/maps/${mapId}/embed?zoom=1.0"></iframe>`} defaultValue={`<iframe style="width:600px;height:400px;border:1px solid black" src="${baseUrl}/c/maps/${mapId}/embed?zoom=1.0"></iframe>`}
/> />
</TabPanel> </TabPanel>
<TabPanel value="1"> <TabPanel value="1">
<Typography variant="subtitle2"> <Typography variant="subtitle2">
<FormattedMessage <FormattedMessage
id="publish.public-url-msg" id="publish.public-url-msg"
defaultMessage="Copy and paste the link below to share your map with colleagues:" defaultMessage="Copy and paste the link below to share your map with colleagues:"
/> />
</Typography> </Typography>
<TextareaAutosize <TextareaAutosize
className={classes.textarea} className={classes.textarea}
readOnly={true} readOnly={true}
spellCheck={false} spellCheck={false}
maxRows={1} maxRows={1}
defaultValue={`${baseUrl}/c/maps/${mapId}/public`} defaultValue={`${baseUrl}/c/maps/${mapId}/public`}
/> />
</TabPanel> </TabPanel>
</TabContext> </TabContext>
</div>
</BaseDialog>
</div> </div>
); </BaseDialog>
</div>
);
}; };
export default PublishDialog; export default PublishDialog;

View File

@ -10,104 +10,104 @@ import BaseDialog from '../base-dialog';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
export type RenameModel = { export type RenameModel = {
id: number; id: number;
title: string; title: string;
description?: string; description?: string;
}; };
const defaultModel: RenameModel = { title: '', description: '', id: -1 }; const defaultModel: RenameModel = { title: '', description: '', id: -1 };
const RenameDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const RenameDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const service: Client = useSelector(activeInstance); const service: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<RenameModel>(defaultModel); const [model, setModel] = React.useState<RenameModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl(); const intl = useIntl();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const mutation = useMutation<RenameModel, ErrorInfo, RenameModel>( const mutation = useMutation<RenameModel, ErrorInfo, RenameModel>(
(model: RenameModel) => { (model: RenameModel) => {
const { id, ...rest } = model; const { id, ...rest } = model;
return service.renameMap(id, rest).then(() => model); return service.renameMap(id, rest).then(() => model);
}, },
{ {
onSuccess: () => { onSuccess: () => {
handleOnMutationSuccess(onClose, queryClient); handleOnMutationSuccess(onClose, queryClient);
}, },
onError: (error) => { onError: (error) => {
setError(error); setError(error);
}, },
} },
); );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose(); onClose();
setModel(defaultModel); setModel(defaultModel);
setError(undefined); setError(undefined);
}; };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault(); event.preventDefault();
mutation.mutate(model); mutation.mutate(model);
}; };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault(); event.preventDefault();
const name = event.target.name; const name = event.target.name;
const value = event.target.value; const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value }); setModel({ ...model, [name as keyof BasicMapInfo]: value });
}; };
const { map } = fetchMapById(mapId); const { map } = fetchMapById(mapId);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
setModel(map); setModel(map);
} }
}, [mapId]); }, [mapId]);
return ( return (
<div> <div>
<BaseDialog <BaseDialog
onClose={handleOnClose} onClose={handleOnClose}
onSubmit={handleOnSubmit} onSubmit={handleOnSubmit}
error={error} error={error}
title={intl.formatMessage({ id: 'rename.title', defaultMessage: 'Rename' })} title={intl.formatMessage({ id: 'rename.title', defaultMessage: 'Rename' })}
description={intl.formatMessage({ description={intl.formatMessage({
id: 'rename.description', id: 'rename.description',
defaultMessage: 'Please, fill the new map name and description.', defaultMessage: 'Please, fill the new map name and description.',
})} })}
submitButton={intl.formatMessage({ id: 'rename.title', defaultMessage: 'Rename' })} submitButton={intl.formatMessage({ id: 'rename.title', defaultMessage: 'Rename' })}
> >
<FormControl fullWidth={true}> <FormControl fullWidth={true}>
<Input <Input
name="title" name="title"
type="text" type="text"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'action.rename-name-placeholder', id: 'action.rename-name-placeholder',
defaultMessage: 'Name', defaultMessage: 'Name',
})} })}
value={model.title} value={model.title}
onChange={handleOnChange} onChange={handleOnChange}
error={error} error={error}
fullWidth={true} fullWidth={true}
/> />
<Input <Input
name="description" name="description"
type="text" type="text"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'action.rename-description-placeholder', id: 'action.rename-description-placeholder',
defaultMessage: 'Description', defaultMessage: 'Description',
})} })}
value={model.description} value={model.description}
onChange={handleOnChange} onChange={handleOnChange}
required={false} required={false}
fullWidth={true} fullWidth={true}
/> />
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
</div> </div>
); );
}; };
export default RenameDialog; export default RenameDialog;

View File

@ -26,247 +26,233 @@ import RoleIcon from '../../role-icon';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
type ShareModel = { type ShareModel = {
emails: string; emails: string;
role: 'editor' | 'viewer'; role: 'editor' | 'viewer';
message: string; message: string;
}; };
const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' }; const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' };
const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const classes = useStyles(); const classes = useStyles();
const [showMessage, setShowMessage] = React.useState<boolean>(false); const [showMessage, setShowMessage] = React.useState<boolean>(false);
const [model, setModel] = React.useState<ShareModel>(defaultModel); const [model, setModel] = React.useState<ShareModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const deleteMutation = useMutation( const deleteMutation = useMutation(
(email: string) => { (email: string) => {
return client.deleteMapPermission(mapId, email); return client.deleteMapPermission(mapId, email);
}, },
{ {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel);
},
onError: (error: ErrorInfo) => {
setError(error);
},
}
);
const splitEmail = (emails: string): string[] => {
return emails.split(/,|;/)
.map(e => e.trim().replace(/\s/g, ''))
.filter(e => e.trim().length > 0);
}
const addMutation = useMutation(
(model: ShareModel) => {
const emails = splitEmail(model.emails);
const permissions = emails.map((email: string) => {
return { email: email, role: model.role };
});
return client.addMapPermissions(mapId, model.message, permissions);
},
{
onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel);
},
onError: (error: ErrorInfo) => {
setError(error);
},
}
);
const handleOnClose = (): void => {
// Invalidate cache ...
queryClient.invalidateQueries(`perm-${mapId}`); queryClient.invalidateQueries(`perm-${mapId}`);
onClose(); setModel(defaultModel);
},
onError: (error: ErrorInfo) => {
setError(error);
},
},
);
}; const splitEmail = (emails: string): string[] => {
return emails
.split(/,|;/)
.map((e) => e.trim().replace(/\s/g, ''))
.filter((e) => e.trim().length > 0);
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const addMutation = useMutation(
event.preventDefault(); (model: ShareModel) => {
const emails = splitEmail(model.emails);
const permissions = emails.map((email: string) => {
return { email: email, role: model.role };
});
return client.addMapPermissions(mapId, model.message, permissions);
},
{
onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel);
},
onError: (error: ErrorInfo) => {
setError(error);
},
},
);
const name = event.target.name; const handleOnClose = (): void => {
const value = event.target.value; // Invalidate cache ...
setModel({ ...model, [name as keyof ShareModel]: value }); queryClient.invalidateQueries(`perm-${mapId}`);
event.stopPropagation(); onClose();
}; };
const handleOnAddClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.stopPropagation(); event.preventDefault();
addMutation.mutate(model);
event.stopPropagation();
};
const handleOnDeleteClick = ( const name = event.target.name;
event: React.MouseEvent<HTMLButtonElement, MouseEvent>, const value = event.target.value;
email: string setModel({ ...model, [name as keyof ShareModel]: value });
): void => { event.stopPropagation();
event.stopPropagation(); };
deleteMutation.mutate(email);
};
const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(`perm-${mapId}`, const handleOnAddClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
() => { event.stopPropagation();
return client.fetchMapPermissions(mapId); addMutation.mutate(model);
} event.stopPropagation();
); };
const formatName = (perm: Permission): string => { const handleOnDeleteClick = (
return perm.name ? `${perm.name}<${perm.email}>` : perm.email; event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
}; email: string,
): void => {
event.stopPropagation();
deleteMutation.mutate(email);
};
// very basic email validation, just make sure the basic syntax is fine const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(
const isValid = splitEmail(model.emails) `perm-${mapId}`,
.every(str => /\S+@\S+\.\S+/.test((str || '') () => {
.trim())); return client.fetchMapPermissions(mapId);
},
);
return ( const formatName = (perm: Permission): string => {
<div> return perm.name ? `${perm.name}<${perm.email}>` : perm.email;
<BaseDialog };
onClose={handleOnClose}
title={intl.formatMessage({
id: 'share.delete-title',
defaultMessage: 'Share with people',
})}
description={intl.formatMessage({
id: 'share.delete-description',
defaultMessage:
'Invite people to collaborate with you in the creation of your mindmap. They will be notified by email. ',
})}
PaperProps={{ classes: { root: classes.paper } }}
error={error}
>
<div className={classes.actionContainer}>
<TextField
id="emails"
name="emails"
required={true}
style={{ width: '300px' }}
size="small"
type="email"
variant="outlined"
placeholder="Add collaborator email"
label="Emails"
onChange={handleOnChange}
value={model.emails}
/>
<Select // very basic email validation, just make sure the basic syntax is fine
variant="outlined" const isValid = splitEmail(model.emails).every((str) => /\S+@\S+\.\S+/.test((str || '').trim()));
onChange={handleOnChange}
value={model.role}
name="role"
style={{ margin: '0px 10px' }}
>
<MenuItem value="editor">
<FormattedMessage id="share.can-edit" defaultMessage="Can edit" />
</MenuItem>
<MenuItem value="viewer">
<FormattedMessage id="share.can-view" defaultMessage="Can view" />
</MenuItem>
</Select>
<FormControlLabel return (
value="start" <div>
onChange={(event, value) => { <BaseDialog
setShowMessage(value); onClose={handleOnClose}
}} title={intl.formatMessage({
style={{ fontSize: '5px' }} id: 'share.delete-title',
control={<Checkbox color="primary" />} defaultMessage: 'Share with people',
label={ })}
<Typography variant="subtitle2"> description={intl.formatMessage({
<FormattedMessage id: 'share.delete-description',
id="share.add-message" defaultMessage:
defaultMessage="Add message" 'Invite people to collaborate with you in the creation of your mindmap. They will be notified by email. ',
/> })}
</Typography> PaperProps={{ classes: { root: classes.paper } }}
} error={error}
labelPlacement="end" >
/> <div className={classes.actionContainer}>
<TextField
id="emails"
name="emails"
required={true}
style={{ width: '300px' }}
size="small"
type="email"
variant="outlined"
placeholder="Add collaborator email"
label="Emails"
onChange={handleOnChange}
value={model.emails}
/>
<Button <Select
color="primary" variant="outlined"
type="button" onChange={handleOnChange}
variant="contained" value={model.role}
disableElevation={true} name="role"
onClick={handleOnAddClick} style={{ margin: '0px 10px' }}
disabled={!isValid} >
> <MenuItem value="editor">
<FormattedMessage id="share.add-button" defaultMessage="Add" /> <FormattedMessage id="share.can-edit" defaultMessage="Can edit" />
</Button> </MenuItem>
<MenuItem value="viewer">
<FormattedMessage id="share.can-view" defaultMessage="Can view" />
</MenuItem>
</Select>
{showMessage && ( <FormControlLabel
<TextField value="start"
multiline onChange={(event, value) => {
rows={3} setShowMessage(value);
maxRows={3} }}
className={classes.textArea} style={{ fontSize: '5px' }}
variant="filled" control={<Checkbox color="primary" />}
name="message" label={
onChange={handleOnChange} <Typography variant="subtitle2">
value={model.message} <FormattedMessage id="share.add-message" defaultMessage="Add message" />
label={intl.formatMessage({ </Typography>
id: 'share.message', }
defaultMessage: 'Message', labelPlacement="end"
})} />
/>
)}
</div>
{!isLoading && ( <Button
<Paper elevation={1} className={classes.listPaper} variant="outlined"> color="primary"
<List> type="button"
{permissions && variant="contained"
permissions.map((permission) => { disableElevation={true}
return ( onClick={handleOnAddClick}
<ListItem disabled={!isValid}
key={permission.email} >
role={undefined} <FormattedMessage id="share.add-button" defaultMessage="Add" />
dense </Button>
button
>
<ListItemText
id={permission.email}
primary={formatName(permission)}
/>
<RoleIcon role={permission.role} /> {showMessage && (
<ListItemSecondaryAction> <TextField
<Tooltip multiline
title={ rows={3}
<FormattedMessage maxRows={3}
id="share.delete" className={classes.textArea}
defaultMessage="Delete collaborator" variant="filled"
/> name="message"
} onChange={handleOnChange}
> value={model.message}
<IconButton label={intl.formatMessage({
edge="end" id: 'share.message',
disabled={permission.role == 'owner'} defaultMessage: 'Message',
onClick={(e) => })}
handleOnDeleteClick(e, permission.email) />
} )}
size="large">
<DeleteIcon />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</Paper>
)}
</BaseDialog>
</div> </div>
);
{!isLoading && (
<Paper elevation={1} className={classes.listPaper} variant="outlined">
<List>
{permissions &&
permissions.map((permission) => {
return (
<ListItem key={permission.email} role={undefined} dense button>
<ListItemText id={permission.email} primary={formatName(permission)} />
<RoleIcon role={permission.role} />
<ListItemSecondaryAction>
<Tooltip
title={
<FormattedMessage
id="share.delete"
defaultMessage="Delete collaborator"
/>
}
>
<IconButton
edge="end"
disabled={permission.role == 'owner'}
onClick={(e) => handleOnDeleteClick(e, permission.email)}
size="large"
>
<DeleteIcon />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</Paper>
)}
</BaseDialog>
</div>
);
}; };
export default ShareDialog; export default ShareDialog;

View File

@ -14,92 +14,85 @@ import ListItemIcon from '@mui/material/ListItemIcon';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
const HelpMenu = (): React.ReactElement => { const HelpMenu = (): React.ReactElement => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const intl = useIntl(); const intl = useIntl();
const handleMenu = (event: React.MouseEvent<HTMLElement>) => { const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
return ( return (
<span> <span>
<Tooltip <Tooltip
arrow={true} arrow={true}
title={intl.formatMessage({ id: 'help.support', defaultMessage: 'Support' })} title={intl.formatMessage({ id: 'help.support', defaultMessage: 'Support' })}
> >
<IconButton aria-haspopup="true" onClick={handleMenu} size="large"> <IconButton aria-haspopup="true" onClick={handleMenu} size="large">
<Help /> <Help />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Menu <Menu
id="appbar-profile" id="appbar-profile"
anchorEl={anchorEl} anchorEl={anchorEl}
keepMounted keepMounted
open={open} open={open}
onClose={handleClose} onClose={handleClose}
anchorOrigin={{ anchorOrigin={{
vertical: 'bottom', vertical: 'bottom',
horizontal: 'right', horizontal: 'right',
}} }}
transformOrigin={{ transformOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'right', horizontal: 'right',
}} }}
> >
<MenuItem onClick={handleClose}> <MenuItem onClick={handleClose}>
<Link <Link
color="textSecondary" color="textSecondary"
href="https://www.wisemapping.com/termsofuse.html" href="https://www.wisemapping.com/termsofuse.html"
target="help" target="help"
> >
<ListItemIcon> <ListItemIcon>
<PolicyOutlined fontSize="small" /> <PolicyOutlined fontSize="small" />
</ListItemIcon> </ListItemIcon>
<FormattedMessage <FormattedMessage id="footer.termsandconditions" defaultMessage="Term And Conditions" />
id="footer.termsandconditions" </Link>
defaultMessage="Term And Conditions" </MenuItem>
/>
</Link>
</MenuItem>
<MenuItem onClick={handleClose}> <MenuItem onClick={handleClose}>
<Link color="textSecondary" href="mailto:team@wisemapping.com"> <Link color="textSecondary" href="mailto:team@wisemapping.com">
<ListItemIcon> <ListItemIcon>
<EmailOutlined fontSize="small" /> <EmailOutlined fontSize="small" />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="footer.contactus" defaultMessage="Contact Us" /> <FormattedMessage id="footer.contactus" defaultMessage="Contact Us" />
</Link> </Link>
</MenuItem> </MenuItem>
<MenuItem onClick={handleClose}> <MenuItem onClick={handleClose}>
<Link color="textSecondary" href="mailto:feedback@wisemapping.com"> <Link color="textSecondary" href="mailto:feedback@wisemapping.com">
<ListItemIcon> <ListItemIcon>
<FeedbackOutlined fontSize="small" /> <FeedbackOutlined fontSize="small" />
</ListItemIcon> </ListItemIcon>
<FormattedMessage id="footer.feedback" defaultMessage="Feedback" /> <FormattedMessage id="footer.feedback" defaultMessage="Feedback" />
</Link> </Link>
</MenuItem> </MenuItem>
<MenuItem onClick={handleClose}> <MenuItem onClick={handleClose}>
<Link <Link color="textSecondary" href="https://www.wisemapping.com/aboutus.html" target="help">
color="textSecondary" <ListItemIcon>
href="https://www.wisemapping.com/aboutus.html" <EmojiPeopleOutlined fontSize="small" />
target="help" </ListItemIcon>
> <FormattedMessage id="footer.aboutus" defaultMessage="About Us" />
<ListItemIcon> </Link>
<EmojiPeopleOutlined fontSize="small" /> </MenuItem>
</ListItemIcon> </Menu>
<FormattedMessage id="footer.aboutus" defaultMessage="About Us" /> </span>
</Link> );
</MenuItem>
</Menu>
</span>
);
}; };
export default HelpMenu; export default HelpMenu;

View File

@ -18,134 +18,133 @@ import DialogActions from '@mui/material/DialogActions';
import Divider from '@mui/material/Divider'; import Divider from '@mui/material/Divider';
const LanguageMenu = (): React.ReactElement => { const LanguageMenu = (): React.ReactElement => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [openHelpDialog, setHelpDialogOpen] = React.useState<boolean>(false); const [openHelpDialog, setHelpDialogOpen] = React.useState<boolean>(false);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const intl = useIntl(); const intl = useIntl();
const mutation = useMutation((locale: LocaleCode) => client.updateAccountLanguage(locale), { const mutation = useMutation((locale: LocaleCode) => client.updateAccountLanguage(locale), {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries('account'); queryClient.invalidateQueries('account');
handleClose();
},
onError: (error) => {
console.error(`Unexpected error ${error}`);
},
});
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleOnClick = (event: React.MouseEvent<HTMLElement>) => {
const localeCode = event.target['id'];
mutation.mutate(localeCode);
};
const userLocale = AppI18n.getUserLocale();
return (
<span>
<Tooltip
arrow={true}
title={intl.formatMessage({
id: 'language.change',
defaultMessage: 'Change Language',
})}
>
<Button
size="small"
variant="outlined"
disableElevation={true}
color="primary"
style={{ borderColor: 'gray', color: 'gray' }}
onClick={handleMenu}
startIcon={<TranslateTwoTone style={{ color: 'inherit' }} />}
>
{userLocale.label}
</Button>
</Tooltip>
<Menu
id="appbar-language"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<MenuItem onClick={handleOnClick} id={Locales.EN.code}>
{Locales.EN.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.ES.code}>
{Locales.ES.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.DE.code}>
{Locales.DE.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.FR.code}>
{Locales.FR.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.RU.code}>
{Locales.RU.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.ZH.code}>
{Locales.ZH.label}
</MenuItem>
<Divider />
<MenuItem
onClick={() => {
handleClose(); handleClose();
}, setHelpDialogOpen(true);
onError: (error) => { }}
console.error(`Unexpected error ${error}`); >
}, <FormattedMessage id="language.help" defaultMessage="Help to Translate" />
}); </MenuItem>
</Menu>
const handleMenu = (event: React.MouseEvent<HTMLElement>) => { {openHelpDialog && <HelpUsToTranslateDialog onClose={() => setHelpDialogOpen(false)} />}
setAnchorEl(event.currentTarget); </span>
}; );
const handleClose = () => {
setAnchorEl(null);
};
const handleOnClick = (event: React.MouseEvent<HTMLElement>) => {
const localeCode = event.target['id'];
mutation.mutate(localeCode);
};
const userLocale = AppI18n.getUserLocale();
return (
<span>
<Tooltip
arrow={true}
title={intl.formatMessage({
id: 'language.change',
defaultMessage: 'Change Language',
})}
>
<Button
size="small"
variant="outlined"
disableElevation={true}
color="primary"
style={{ borderColor: 'gray', color: 'gray' }}
onClick={handleMenu}
startIcon={<TranslateTwoTone style={{ color: 'inherit' }} />}
>
{userLocale.label}
</Button>
</Tooltip>
<Menu
id="appbar-language"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<MenuItem onClick={handleOnClick} id={Locales.EN.code}>
{Locales.EN.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.ES.code}>
{Locales.ES.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.DE.code}>
{Locales.DE.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.FR.code}>
{Locales.FR.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.RU.code}>
{Locales.RU.label}
</MenuItem>
<MenuItem onClick={handleOnClick} id={Locales.ZH.code}>
{Locales.ZH.label}
</MenuItem>
<Divider />
<MenuItem
onClick={() => {
handleClose();
setHelpDialogOpen(true);
}}
>
<FormattedMessage id="language.help" defaultMessage="Help to Translate" />
</MenuItem>
</Menu>
{openHelpDialog && <HelpUsToTranslateDialog onClose={() => setHelpDialogOpen(false)} />}
</span>
);
}; };
type HelpUsToTranslateDialogProp = { type HelpUsToTranslateDialogProp = {
onClose: () => void; onClose: () => void;
}; };
const HelpUsToTranslateDialog = ({ onClose }: HelpUsToTranslateDialogProp) => { const HelpUsToTranslateDialog = ({ onClose }: HelpUsToTranslateDialogProp) => {
return ( return (
<Dialog open={true} onClose={onClose}> <Dialog open={true} onClose={onClose}>
<DialogTitle>Help us to support more languages !</DialogTitle> <DialogTitle>Help us to support more languages !</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText> <DialogContentText>
We need your help !. If you are interested, send us an email to We need your help !. If you are interested, send us an email to team@wisemapping.com.
team@wisemapping.com. </DialogContentText>
</DialogContentText> </DialogContent>
</DialogContent> <DialogActions>
<DialogActions> <Button autoFocus onClick={onClose}>
<Button autoFocus onClick={onClose}> Close
Close </Button>
</Button> </DialogActions>
</DialogActions> </Dialog>
</Dialog> );
);
}; };
export default LanguageMenu; export default LanguageMenu;

File diff suppressed because it is too large Load Diff

View File

@ -8,38 +8,52 @@ import BaseDialog from '../../action-dispatcher/base-dialog';
import { Label } from '../../../../classes/client'; import { Label } from '../../../../classes/client';
export type LabelDeleteConfirmType = { export type LabelDeleteConfirmType = {
label: Label; label: Label;
onClose: () => void; onClose: () => void;
onConfirm: () => void; onConfirm: () => void;
}; };
const LabelDeleteConfirm = ({ label, onClose, onConfirm }: LabelDeleteConfirmType): React.ReactElement => { const LabelDeleteConfirm = ({
const intl = useIntl(); label,
onClose,
onConfirm,
}: LabelDeleteConfirmType): React.ReactElement => {
const intl = useIntl();
return ( return (
<div> <div>
<BaseDialog <BaseDialog
onClose={onClose} onClose={onClose}
onSubmit={onConfirm} onSubmit={onConfirm}
title={intl.formatMessage({ id: 'label.delete-title', defaultMessage: 'Confirm label deletion' })} title={intl.formatMessage({
submitButton={intl.formatMessage({ id: 'label.delete-title',
id: 'action.delete-title', defaultMessage: 'Confirm label deletion',
defaultMessage: 'Delete', })}
})} submitButton={intl.formatMessage({
> id: 'action.delete-title',
<Alert severity="warning"> defaultMessage: 'Delete',
<AlertTitle>{intl.formatMessage({ id: 'label.delete-title', defaultMessage: 'Confirm label deletion' })}</AlertTitle> })}
<span> >
<Typography fontWeight="bold" component="span">{label.title} </Typography> <Alert severity="warning">
<FormattedMessage <AlertTitle>
id="label.delete-description" {intl.formatMessage({
defaultMessage="will be deleted, including its associations to all existing maps. Do you want to continue?" id: 'label.delete-title',
/> defaultMessage: 'Confirm label deletion',
</span> })}
</Alert> </AlertTitle>
</BaseDialog> <span>
</div> <Typography fontWeight="bold" component="span">
); {label.title}{' '}
</Typography>
<FormattedMessage
id="label.delete-description"
defaultMessage="will be deleted, including its associations to all existing maps. Do you want to continue?"
/>
</span>
</Alert>
</BaseDialog>
</div>
);
}; };
export default LabelDeleteConfirm; export default LabelDeleteConfirm;

View File

@ -12,44 +12,44 @@ import AddLabelDialog from '../../action-dispatcher/add-label-dialog';
import { LabelListContainer } from './styled'; import { LabelListContainer } from './styled';
export type LabelSelectorProps = { export type LabelSelectorProps = {
maps: MapInfo[]; maps: MapInfo[];
onChange: (label: Label, checked: boolean) => void; onChange: (label: Label, checked: boolean) => void;
}; };
export function LabelSelector({ onChange, maps }: LabelSelectorProps): React.ReactElement { export function LabelSelector({ onChange, maps }: LabelSelectorProps): React.ReactElement {
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const { data: labels = [] } = useQuery<unknown, ErrorInfo, Label[]>('labels', async () => const { data: labels = [] } = useQuery<unknown, ErrorInfo, Label[]>('labels', async () =>
client.fetchLabels() client.fetchLabels(),
); );
const checkedLabelIds = labels const checkedLabelIds = labels
.map((l) => l.id) .map((l) => l.id)
.filter((labelId) => maps.every((m) => m.labels.find((l) => l.id === labelId))); .filter((labelId) => maps.every((m) => m.labels.find((l) => l.id === labelId)));
return ( return (
<Container> <Container>
<FormGroup> <FormGroup>
<AddLabelDialog onAdd={(label) => onChange(label, true)} /> <AddLabelDialog onAdd={(label) => onChange(label, true)} />
</FormGroup> </FormGroup>
<LabelListContainer> <LabelListContainer>
{labels.map(({ id, title, color }) => ( {labels.map(({ id, title, color }) => (
<FormControlLabel <FormControlLabel
key={id} key={id}
control={ control={
<Checkbox <Checkbox
id={`${id}`} id={`${id}`}
checked={checkedLabelIds.includes(id)} checked={checkedLabelIds.includes(id)}
onChange={(e) => { onChange={(e) => {
onChange({ id, title, color }, e.target.checked); onChange({ id, title, color }, e.target.checked);
}} }}
name={title} name={title}
color="primary" color="primary"
/> />
} }
label={<LabelComponent label={{ id, title, color }} size="big" />} label={<LabelComponent label={{ id, title, color }} size="big" />}
/> />
))} ))}
</LabelListContainer> </LabelListContainer>
</Container> </Container>
); );
} }

View File

@ -6,33 +6,40 @@ import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
import DeleteIcon from '@mui/icons-material/Clear'; import DeleteIcon from '@mui/icons-material/Clear';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
type LabelSize = 'small' | 'big'; type LabelSize = 'small' | 'big';
type LabelComponentProps = { label: Label; onDelete?: (label: Label) => void; size?: LabelSize }; type LabelComponentProps = { label: Label; onDelete?: (label: Label) => void; size?: LabelSize };
export default function LabelComponent({ label, onDelete, size = 'small' }: LabelComponentProps): React.ReactElement<LabelComponentProps> { export default function LabelComponent({
const iconSize = size === 'small' ? { label,
height: '0.6em', width: '0.6em' onDelete,
} : { height: '0.9em', width: '0.9em' }; size = 'small',
}: LabelComponentProps): React.ReactElement<LabelComponentProps> {
const iconSize =
size === 'small'
? {
height: '0.6em',
width: '0.6em',
}
: { height: '0.9em', width: '0.9em' };
return ( return (
<LabelContainer color={label.color}> <LabelContainer color={label.color}>
<LabelTwoTone htmlColor={label.color} style={iconSize} /> <LabelTwoTone htmlColor={label.color} style={iconSize} />
<LabelText>{label.title}</LabelText> <LabelText>{label.title}</LabelText>
{onDelete && ( {onDelete && (
<IconButton <IconButton
color="default" color="default"
size="small" size="small"
aria-label="delete tag" aria-label="delete tag"
component="span" component="span"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onDelete(label); onDelete(label);
}} }}
> >
<DeleteIcon style={iconSize} /> <DeleteIcon style={iconSize} />
</IconButton> </IconButton>
)} )}
</LabelContainer> </LabelContainer>
); );
} }

View File

@ -7,21 +7,22 @@ import DeleteIcon from '@mui/icons-material/Clear';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
type Props = { type Props = {
labels: Label[], labels: Label[];
onDelete: (label: Label) => void, onDelete: (label: Label) => void;
}; };
export function LabelsCell({ labels, onDelete }: Props): React.ReactElement<Props> { export function LabelsCell({ labels, onDelete }: Props): React.ReactElement<Props> {
return ( return (
<> <>
{labels.map(label => ( {labels.map((label) => (
<LabelContainer <LabelContainer key={label.id} color={label.color}>
key={label.id}
color={label.color}
>
<LabelTwoTone htmlColor={label.color} style={{ height: '0.6em', width: '0.6em' }} /> <LabelTwoTone htmlColor={label.color} style={{ height: '0.6em', width: '0.6em' }} />
<LabelText>{ label.title }</LabelText> <LabelText>{label.title}</LabelText>
<IconButton color="default" size='small' aria-label="delete tag" component="span" <IconButton
color="default"
size="small"
aria-label="delete tag"
component="span"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onDelete(label); onDelete(label);
@ -29,7 +30,7 @@ export function LabelsCell({ labels, onDelete }: Props): React.ReactElement<Prop
> >
<DeleteIcon style={{ height: '0.6em', width: '0.6em' }} /> <DeleteIcon style={{ height: '0.6em', width: '0.6em' }} />
</IconButton> </IconButton>
</LabelContainer> </LabelContainer>
))} ))}
</> </>
); );

View File

@ -9,40 +9,31 @@ import { FormattedMessage } from 'react-intl';
import { Role } from '../../../classes/client'; import { Role } from '../../../classes/client';
type RoleIconProps = { type RoleIconProps = {
role: Role; role: Role;
}; };
const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => { const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => {
return ( return (
<span> <span>
{role == 'owner' && ( {role == 'owner' && (
<Tooltip <Tooltip title={<FormattedMessage id="role.owner" defaultMessage="Owner" />} arrow={true}>
title={<FormattedMessage id="role.owner" defaultMessage="Owner" />} <PersonSharpIcon />
arrow={true} </Tooltip>
> )}
<PersonSharpIcon />
</Tooltip>
)}
{role == 'editor' && ( {role == 'editor' && (
<Tooltip <Tooltip title={<FormattedMessage id="role.editor" defaultMessage="Editor" />} arrow={true}>
title={<FormattedMessage id="role.editor" defaultMessage="Editor" />} <EditSharpIcon />
arrow={true} </Tooltip>
> )}
<EditSharpIcon />
</Tooltip>
)}
{role == 'viewer' && ( {role == 'viewer' && (
<Tooltip <Tooltip title={<FormattedMessage id="role.viewer" defaultMessage="Viewer" />} arrow={true}>
title={<FormattedMessage id="role.viewer" defaultMessage="Viewer" />} <VisibilitySharpIcon />
arrow={true} </Tooltip>
> )}
<VisibilitySharpIcon /> </span>
</Tooltip> );
)}
</span>
);
}; };
export default RoleIcon; export default RoleIcon;

View File

@ -20,161 +20,165 @@ import AppConfig from '../../classes/app-config';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
export type Model = { export type Model = {
email: string; email: string;
lastname: string; lastname: string;
firstname: string; firstname: string;
password: string; password: string;
recaptcha: string; recaptcha: string;
}; };
const defaultModel: Model = { email: '', lastname: '', firstname: '', password: '', recaptcha: '' }; const defaultModel: Model = { email: '', lastname: '', firstname: '', password: '', recaptcha: '' };
const RegistrationForm = () => { const RegistrationForm = () => {
const [model, setModel] = useState<Model>(defaultModel); const [model, setModel] = useState<Model>(defaultModel);
const [error, setError] = useState<ErrorInfo>(); const [error, setError] = useState<ErrorInfo>();
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const [captcha, setCaptcha] = useState<any>(); const [captcha, setCaptcha] = useState<any>();
const history = useHistory(); const history = useHistory();
const intl = useIntl(); const intl = useIntl();
const Client: Client = useSelector(activeInstance); const Client: Client = useSelector(activeInstance);
const mutation = useMutation<void, ErrorInfo, Model>( const mutation = useMutation<void, ErrorInfo, Model>(
(model: Model) => Client.registerNewUser({ ...model }), (model: Model) => Client.registerNewUser({ ...model }),
{ {
onSuccess: () => history.push('/c/registration-success'), onSuccess: () => history.push('/c/registration-success'),
onError: (error) => { onError: (error) => {
setError(error); setError(error);
captcha.reset(); captcha.reset();
}, },
} },
); );
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault(); event.preventDefault();
mutation.mutate(model); mutation.mutate(model);
}; };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault(); event.preventDefault();
const name = event.target.name; const name = event.target.name;
const value = event.target.value; const value = event.target.value;
setModel({ ...model, [name as keyof Model]: value }); setModel({ ...model, [name as keyof Model]: value });
}; };
return ( return (
<FormContainer> <FormContainer>
<Typography variant="h4" component="h1"> <Typography variant="h4" component="h1">
<FormattedMessage id="registration.title" defaultMessage="Become a member" /> <FormattedMessage id="registration.title" defaultMessage="Become a member" />
</Typography> </Typography>
<Typography paragraph> <Typography paragraph>
<FormattedMessage <FormattedMessage
id="registration.desc" id="registration.desc"
defaultMessage="Signing up is free and just take a moment " defaultMessage="Signing up is free and just take a moment "
/> />
</Typography> </Typography>
<FormControl> <FormControl>
<form onSubmit={handleOnSubmit}> <form onSubmit={handleOnSubmit}>
<GlobalError error={error} /> <GlobalError error={error} />
<Input <Input
name="email" name="email"
type="email" type="email"
onChange={handleOnChange} onChange={handleOnChange}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'registration.email', id: 'registration.email',
defaultMessage: 'Email', defaultMessage: 'Email',
})} })}
autoComplete="email" autoComplete="email"
error={error} error={error}
/> />
<Input <Input
name="firstname" name="firstname"
type="text" type="text"
onChange={handleOnChange} onChange={handleOnChange}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'registration.firstname', id: 'registration.firstname',
defaultMessage: 'First Name', defaultMessage: 'First Name',
})} })}
autoComplete="given-name" autoComplete="given-name"
error={error} error={error}
/> />
<Input <Input
name="lastname" name="lastname"
type="text" type="text"
onChange={handleOnChange} onChange={handleOnChange}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'registration.lastname', id: 'registration.lastname',
defaultMessage: 'Last Name', defaultMessage: 'Last Name',
})} })}
autoComplete="family-name" autoComplete="family-name"
error={error} error={error}
/> />
<Input <Input
name="password" name="password"
type="password" type="password"
onChange={handleOnChange} onChange={handleOnChange}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'registration.password', id: 'registration.password',
defaultMessage: 'Password', defaultMessage: 'Password',
})} })}
autoComplete="new-password" autoComplete="new-password"
error={error} error={error}
/> />
{AppConfig.isRecaptcha2Enabled() && {AppConfig.isRecaptcha2Enabled() && (
<div style={{ width: '330px', padding: '5px 0px 5px 20px' }}> <div style={{ width: '330px', padding: '5px 0px 5px 20px' }}>
<ReCAPTCHA <ReCAPTCHA
ref={el => setCaptcha(el)} ref={(el) => setCaptcha(el)}
sitekey={AppConfig.getRecaptcha2SiteKey()} sitekey={AppConfig.getRecaptcha2SiteKey()}
onChange={(value: string) => { onChange={(value: string) => {
model.recaptcha = value; model.recaptcha = value;
setModel(model); setModel(model);
}} }}
/> />
</div> </div>
} )}
<div style={{ fontSize: '12px', padding: '10px 0px' }}> <div style={{ fontSize: '12px', padding: '10px 0px' }}>
<FormattedMessage <FormattedMessage
id="registration.termandconditions" id="registration.termandconditions"
defaultMessage="Terms of Client: Please check the WiseMapping Account information you've entered above, and review the Terms of Client here. By clicking on 'Register' below you are agreeing to the Terms of Client above and the Privacy Policy" defaultMessage="Terms of Client: Please check the WiseMapping Account information you've entered above, and review the Terms of Client here. By clicking on 'Register' below you are agreeing to the Terms of Client above and the Privacy Policy"
/> />
</div> </div>
<SubmitButton <SubmitButton
value={intl.formatMessage({ value={intl.formatMessage({
id: 'registration.register', id: 'registration.register',
defaultMessage: 'Register', defaultMessage: 'Register',
})} })}
/> />
</form> </form>
</FormControl> </FormControl>
</FormContainer> </FormContainer>
); );
}; };
const RegistationPage = (): React.ReactElement => { const RegistationPage = (): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
useEffect(() => { useEffect(() => {
document.title = intl.formatMessage({ document.title = intl.formatMessage({
id: 'registration.page-title', id: 'registration.page-title',
defaultMessage: 'Registration | WiseMapping', defaultMessage: 'Registration | WiseMapping',
}); });
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Registration:Init' }); ReactGA.send({
}, []); hitType: 'pageview',
page: window.location.pathname,
title: 'Registration:Init',
});
}, []);
return ( return (
<div> <div>
<Header type="only-signin" /> <Header type="only-signin" />
<RegistrationForm /> <RegistrationForm />
<Footer /> <Footer />
</div> </div>
); );
}; };
export default RegistationPage; export default RegistationPage;

View File

@ -8,47 +8,53 @@ import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
const RegistrationSuccessPage = (): React.ReactElement => { const RegistrationSuccessPage = (): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
useEffect(() => { useEffect(() => {
document.title = intl.formatMessage({ id: 'registation.success-title', defaultMessage: 'Registation Success | WiseMapping' }); document.title = intl.formatMessage({
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Registration:Success' }); id: 'registation.success-title',
defaultMessage: 'Registation Success | WiseMapping',
}); });
ReactGA.send({
hitType: 'pageview',
page: window.location.pathname,
title: 'Registration:Success',
});
});
return ( return (
<div> <div>
<Header type="none" /> <Header type="none" />
<FormContainer> <FormContainer>
<Typography variant="h4" component="h1"> <Typography variant="h4" component="h1">
<FormattedMessage <FormattedMessage
id="resetpassword.success.title" id="resetpassword.success.title"
defaultMessage="Your account has been created successfully" defaultMessage="Your account has been created successfully"
/> />
</Typography> </Typography>
<Typography paragraph> <Typography paragraph>
<FormattedMessage <FormattedMessage
id="registration.success.desc" id="registration.success.desc"
defaultMessage="Click 'Sign In' button below and start creating mind maps." defaultMessage="Click 'Sign In' button below and start creating mind maps."
/> />
</Typography> </Typography>
<Button <Button
color="primary" color="primary"
size="medium" size="medium"
variant="contained" variant="contained"
component={RouterLink} component={RouterLink}
to="/c/login" to="/c/login"
disableElevation={true} disableElevation={true}
> >
<FormattedMessage id="login.signin" defaultMessage="Sign In" /> <FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button> </Button>
</FormContainer> </FormContainer>
<Footer /> <Footer />
</div> </div>
); );
}; };
export default RegistrationSuccessPage; export default RegistrationSuccessPage;

View File

@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
import App from './app'; import App from './app';
async function bootstrapApplication() { async function bootstrapApplication() {
ReactDOM.render(<App />, document.getElementById('root') as HTMLElement); ReactDOM.render(<App />, document.getElementById('root') as HTMLElement);
} }
bootstrapApplication(); bootstrapApplication();