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

View File

@ -6,4 +6,180 @@
* Licensed under the Apache License v2.0
* 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-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-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, .15), 0 1px 1px rgba(0, 0, 0, .075);
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
-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, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
}
.btn-default:active,
.btn-primary:active,
@ -26,8 +26,8 @@
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
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, 0.125);
}
.btn:active,
.btn.active {
@ -36,7 +36,7 @@
.btn-default {
text-shadow: 0 1px 0 #fff;
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(enabled = false);
background-repeat: repeat-x;
@ -55,7 +55,7 @@
}
.btn-primary {
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(enabled = false);
background-repeat: repeat-x;
@ -73,7 +73,7 @@
}
.btn-success {
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(enabled = false);
background-repeat: repeat-x;
@ -91,7 +91,7 @@
}
.btn-info {
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(enabled = false);
background-repeat: repeat-x;
@ -109,7 +109,7 @@
}
.btn-warning {
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(enabled = false);
background-repeat: repeat-x;
@ -127,7 +127,7 @@
}
.btn-danger {
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(enabled = false);
background-repeat: repeat-x;
@ -145,14 +145,14 @@
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
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, 0.075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #e8e8e8;
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);
background-repeat: repeat-x;
}
@ -161,50 +161,50 @@
.dropdown-menu > .active > a:focus {
background-color: #357ebd;
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);
background-repeat: repeat-x;
}
.navbar-default {
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(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
-webkit-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, .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, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
}
.navbar-default .navbar-nav > .active > a {
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);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
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, 0.075);
}
.navbar-brand,
.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 {
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(enabled = false);
background-repeat: repeat-x;
}
.navbar-inverse .navbar-nav > .active > a {
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);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
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, 0.25);
}
.navbar-inverse .navbar-brand,
.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-fixed-top,
@ -212,136 +212,136 @@
border-radius: 0;
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-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, .25), 0 1px 2px rgba(0, 0, 0, .05);
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
-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, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.alert-success {
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);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
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);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
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);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
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);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
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);
background-repeat: repeat-x;
}
.progress-bar {
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);
background-repeat: repeat-x;
}
.progress-bar-success {
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);
background-repeat: repeat-x;
}
.progress-bar-info {
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);
background-repeat: repeat-x;
}
.progress-bar-warning {
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);
background-repeat: repeat-x;
}
.progress-bar-danger {
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);
background-repeat: repeat-x;
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
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, 0.075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #3071a9;
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);
background-repeat: repeat-x;
border-color: #3278b3;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
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, 0.05);
}
.panel-default > .panel-heading {
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);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
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);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
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);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
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);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
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);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
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);
background-repeat: repeat-x;
}
.well {
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);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-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, .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, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
}
/*# 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 */
.wise-editor .modal {
overflow: hidden;
overflow: hidden;
}
.modal-backdrop {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(0,0,0,0.5);
z-index: 1000;
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}

File diff suppressed because one or more lines are too long

View File

@ -1,103 +1,104 @@
div#header {
width: 100%;
height:50px;
position: absolute;
top: 0;
z-index:1000;
width: 100%;
height: 50px;
position: absolute;
top: 0;
z-index: 1000;
}
div#headerNotifier {
border: 1px solid rgb(241, 163, 39);
background-color: rgb(252, 235, 192);
border-radius: 3px;
position: fixed;
padding: 5px 9px;
color: back;
white-space: nowrap;
margin-top: 5px;
display: none;
bottom: 10px;
border: 1px solid rgb(241, 163, 39);
background-color: rgb(252, 235, 192);
border-radius: 3px;
position: fixed;
padding: 5px 9px;
color: back;
white-space: nowrap;
margin-top: 5px;
display: none;
bottom: 10px;
}
div#toolbarRight {
float: right;
white-space: nowrap;
vertical-align: middle;
justify-content: center;
margin: 6px 10px;
height: 100%;
float: right;
white-space: nowrap;
vertical-align: middle;
justify-content: center;
margin: 6px 10px;
height: 100%;
}
#account {
float: right;
display: inline;
float: right;
display: inline;
}
#account >img {
width: 36x;
height: 36px;
#account > img {
width: 36x;
height: 36px;
}
#accountSettingsPanel{
padding:10px 10px;
#accountSettingsPanel {
padding: 10px 10px;
}
#share {
margin: 0 30px;
float: right;
margin: 0 30px;
float: right;
}
.actionButton {
cursor: pointer;
font-family: Arial, Helvetica, sans-serif;
user-select: none;
vertical-align: middle;
justify-content: center;
padding: 10px 25px;
font-size: 15px;
min-width: 64px;
box-sizing: border-box;
font-weight: 600;
border-radius: 9px;
color: white;
background-color: #ffa800;
cursor: pointer;
font-family: Arial, Helvetica, sans-serif;
user-select: none;
vertical-align: middle;
justify-content: center;
padding: 10px 25px;
font-size: 15px;
min-width: 64px;
box-sizing: border-box;
font-weight: 600;
border-radius: 9px;
color: white;
background-color: #ffa800;
}
.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 {
width: 100%;
height: 50px;
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
background-color: #fff;
min-width: 900px;
overflow: hidden;
width: 100%;
height: 50px;
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
background-color: #fff;
min-width: 900px;
overflow: hidden;
}
div#toolbar .buttonContainer {
height: 50px;
padding-top: 8px;
padding-right: 10px;
padding-left: 10px;
float: left;
border-left: 1px solid lightgray;
height: 50px;
padding-top: 8px;
padding-right: 10px;
padding-left: 10px;
float: left;
border-left: 1px solid lightgray;
}
div#mapName >span {
border-radius: 4px;
float: left;
padding: 8px;
min-width: 30px;
font-weight: bold;
div#mapName > span {
border-radius: 4px;
float: left;
padding: 8px;
min-width: 30px;
font-weight: bold;
}
div#backToList {
height: 24px;
width: 24px;
float: left;
margin: 13px 20px;
height: 24px;
width: 24px;
float: left;
margin: 13px 20px;
}
/******************************************************************************************/
@ -108,120 +109,120 @@ div#toolbar .buttonOn,
div#toolbar .buttonOff,
div#toolbar .buttonActive,
div#toolbar .buttonOn:hover {
width: 28px;
height: 28px;
float: left;
text-align: center;
z-index: 4;
margin-top: 3px;
padding-top: 2px;
padding-left: 2px;
margin-left: 3px;
width: 28px;
height: 28px;
float: left;
text-align: center;
z-index: 4;
margin-top: 3px;
padding-top: 2px;
padding-left: 2px;
margin-left: 3px;
}
div#toolbar .buttonOn:hover {
cursor: pointer;
opacity: 1;
cursor: pointer;
opacity: 1;
}
div#toolbar .buttonOn {
opacity: 0.8;
opacity: 0.8;
}
div#toolbar .buttonOff {
opacity: 0.4;
opacity: 0.4;
}
div#exportAnchor {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
div#toolbar .buttonExtOn,
div#toolbar .buttonExtOff,
div#toolbar .buttonExtActive,
div#toolbar .buttonExtOn:hover {
width: 40px;
height: 28px;
float: left;
text-align: left;
z-index: 4;
margin-top: 3px;
padding-top: 2px;
padding-left: 5px;
margin-left: 3px;
width: 40px;
height: 28px;
float: left;
text-align: left;
z-index: 4;
margin-top: 3px;
padding-top: 2px;
padding-left: 5px;
margin-left: 3px;
}
div#toolbar .buttonExtOn:hover {
opacity: 1;
opacity: 1;
}
div#toolbar .buttonExtActive {
opacity: 1;
opacity: 1;
}
div#toolbar .buttonExtOn {
opacity: 0.8;
cursor: pointer
opacity: 0.8;
cursor: pointer;
}
div#toolbar .buttonExtOff {
opacity: 0.4;
opacity: 0.4;
}
div#exportAnchor {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
/***************************************************************************************************/
/* Other toolbar styles */
/***************************************************************************************************/
.toolbarTip {
background-color: #000000;
padding: 5px 5px;
color: #f5f5f5;
font-size: 11px;
background-color: #000000;
padding: 5px 5px;
color: #f5f5f5;
font-size: 11px;
}
div#colorPalette {
border: 1px solid #bbb4d6;
display: none;
position: absolute;
z-index: 4;
width: 160px;
top: 89px;
border: 1px solid #bbb4d6;
display: none;
position: absolute;
z-index: 4;
width: 160px;
top: 89px;
}
div.toolbarPanelLink,
div.toolbarPanelLinkSelectedLink {
cursor: pointer;
color: black;
margin: 1px;
cursor: pointer;
font-size: 12px;
padding: 5px 10px;
font-weight: bold;
cursor: pointer;
color: black;
margin: 1px;
cursor: pointer;
font-size: 12px;
padding: 5px 10px;
font-weight: bold;
}
div.toolbarPanelLink:hover,
div.toolbarPanelLinkSelectedLink {
cursor: pointer;
background-color: #efefef;
cursor: pointer;
background-color: #efefef;
}
.toolbarPaneTip {
background-color: rgb(228, 226, 210);
padding: 5px 5px;
color: #f5f5f5;
font-size: 11px;
border-radius: 6px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
border: 3px double rgb(190, 190, 190);
background-color: rgb(228, 226, 210);
padding: 5px 5px;
color: #f5f5f5;
font-size: 11px;
border-radius: 6px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
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 ToolbarPropsType = {
editorMode: EditorRenderMode;
onAction: (action: ToolbarActionType) => void;
editorMode: EditorRenderMode;
onAction: (action: ToolbarActionType) => void;
};
export default function Toolbar({
editorMode: editorMode,
onAction,
editorMode: editorMode,
onAction,
}: ToolbarPropsType): React.ReactElement {
const intl = useIntl();
return (
<HeaderContainer className="wise-editor">
<div id="toolbar">
<div id="backToList">
<img src={BackIconSvg} />
</div>
{(editorMode === 'edition-editor' || editorMode === 'edition-owner') && (
<div id="persist" className="buttonContainer">
<ToolbarButton id="save" className="buttonOn">
<img src={SaveSvg} />
</ToolbarButton>
</div>
)}
{(editorMode === 'edition-editor' || editorMode === 'edition-owner' || editorMode === 'showcase') && (
<>
<div id="edit" className="buttonContainer">
<ToolbarButton id="undoEdition" className="buttonOn">
<img src={UndoSvg} />
</ToolbarButton>
<ToolbarButton id="redoEdition" className="buttonOn">
<img src={RedoSvg} />
</ToolbarButton>
</div>
<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>
const intl = useIntl();
return (
<HeaderContainer className="wise-editor">
<div id="toolbar">
<div id="backToList">
<img src={BackIconSvg} />
</div>
{(editorMode === 'edition-editor' || editorMode === 'edition-owner') && (
<div id="persist" className="buttonContainer">
<ToolbarButton id="save" className="buttonOn">
<img src={SaveSvg} />
</ToolbarButton>
</div>
)}
{(editorMode === 'edition-editor' ||
editorMode === 'edition-owner' ||
editorMode === 'showcase') && (
<>
<div id="edit" className="buttonContainer">
<ToolbarButton id="undoEdition" className="buttonOn">
<img src={UndoSvg} />
</ToolbarButton>
<ToolbarButton id="redoEdition" className="buttonOn">
<img src={RedoSvg} />
</ToolbarButton>
</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 */
/********************************************************************************/
@import "bootstrap-prefix.min.css";
@import "bootstrap-fixes.css";
@import 'bootstrap-prefix.min.css';
@import 'bootstrap-fixes.css';
html {
/* avoid bootstrap overriding font-size and breaking Mui */
font-size: initial;
/* avoid bootstrap overriding font-size and breaking Mui */
font-size: initial;
}
body {
width: 100vw;
height: 100vh;
min-width: 100vw;
min-height: 100vh;
margin: 0px;
width: 100vw;
height: 100vh;
min-width: 100vw;
min-height: 100vh;
margin: 0px;
}
.mindplot-root {
width: 100%;
height: 100%;
width: 100%;
height: 100%;
}
div#mindplot {
position: relative;
top: 50px;
left: 0;
width: 100%;
height: 100%;
border: 0;
overflow: hidden;
opacity: 1;
background-color: #f2f2f2;
background-image: linear-gradient(#ebe9e7 1px, transparent 1px), linear-gradient(to right, #ebe9e7 1px, #f2f2f2 1px);
background-size: 50px 50px;
position: relative;
top: 50px;
left: 0;
width: 100%;
height: 100%;
border: 0;
overflow: hidden;
opacity: 1;
background-color: #f2f2f2;
background-image: linear-gradient(#ebe9e7 1px, transparent 1px),
linear-gradient(to right, #ebe9e7 1px, #f2f2f2 1px);
background-size: 50px 50px;
}
.notesTip {
background-color: #dfcf3c;
padding: 5px 15px;
color: #666666;
/*font-weight: bold;*/
/*width: 100px;*/
font-size: 13px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
background-color: #dfcf3c;
padding: 5px 15px;
color: #666666;
/*font-weight: bold;*/
/*width: 100px;*/
font-size: 13px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
}
.linkTip {
background-color: #dfcf3c;
padding: 5px 15px;
color: #666666;
/*font-weight: bold;*/
/*width: 100px;*/
font-size: 13px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
background-color: #dfcf3c;
padding: 5px 15px;
color: #666666;
/*font-weight: bold;*/
/*width: 100px;*/
font-size: 13px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
}
.keyboardShortcutTip {
background-color: black;
padding: 5px 15px;
color: white;
font-weight: bold;
font-size: 11px;
background-color: black;
padding: 5px 15px;
color: white;
font-weight: bold;
font-size: 11px;
}
/** */
/* Modal dialogs definitions */
div.modalDialog {
position: fixed;
top: 50%;
left: 50%;
z-index: 11000;
width: 500px;
margin: -250px 0 0 -250px;
background-color: #ffffff;
border: 1px solid #999;
padding: 10px;
overflow: auto;
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
position: fixed;
top: 50%;
left: 50%;
z-index: 11000;
width: 500px;
margin: -250px 0 0 -250px;
background-color: #ffffff;
border: 1px solid #999;
padding: 10px;
overflow: auto;
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
}
div.modalDialog .content {
padding: 5px 5px;
padding: 5px 5px;
}
div.modalDialog .title {
font-weight: bold;
text-shadow: 1px 1px 0 #fff;
border-bottom: 1px solid #eee;
padding: 5px 15px;
font-size: 18px;
font-weight: bold;
text-shadow: 1px 1px 0 #fff;
border-bottom: 1px solid #eee;
padding: 5px 15px;
font-size: 18px;
}
/*--- End Modal Dialog Form ---*/
.publishModalDialog .content {
height: 420px;
height: 420px;
}
.exportModalDialog .content {
height: 400px;
height: 400px;
}
.shareModalDialog .content {
height: 440px;
height: 440px;
}
div.shareModalDialog {
width: 550px;
width: 550px;
}
.panelIcon {
width: 20px;
height: 20px;
margin-left: 4px;
margin-top: 3px;
cursor: pointer
width: 20px;
height: 20px;
margin-left: 4px;
margin-top: 3px;
cursor: pointer;
}
.panelIcon:hover {
background-color: #efefef;
background-color: #efefef;
}
.wise-editor .popover {
font-size: 13px;
max-width: none;
font-size: 13px;
max-width: none;
}
#floating-panel {
position: fixed;
display: flex;
flex-direction: column;
align-items: flex-end;
bottom: 20px;
right: 20px;
align-items: stretch;
position: fixed;
display: flex;
flex-direction: column;
align-items: flex-end;
bottom: 20px;
right: 20px;
align-items: stretch;
}
div#position {
margin-top: 5px;
margin-top: 5px;
}
#position-button {
cursor: pointer;
border: solid black 1px;
width: 40px;
height: 40px;
background-position: center;
background-repeat: no-repeat;
background-size: 40px 40px;
background-color: #FFF;
border-radius: 8px;
cursor: pointer;
border: solid black 1px;
width: 40px;
height: 40px;
background-position: center;
background-repeat: no-repeat;
background-size: 40px 40px;
background-color: #fff;
border-radius: 8px;
}
#zoom-button {
width: 40px;
border: 0;
width: 40px;
border: 0;
}
#zoom-plus,
#zoom-minus {
border: solid black 1px;
height: 40px;
width: 40px;
background-repeat: no-repeat;
background-size: 40px 40px;
background-position: center;
cursor: pointer;
background-color: #FFF;
border: solid black 1px;
height: 40px;
width: 40px;
background-repeat: no-repeat;
background-size: 40px 40px;
background-position: center;
cursor: pointer;
background-color: #fff;
}
#zoom-plus {
border-radius: 8px 8px 0 0;
border-radius: 8px 8px 0 0;
}
#zoom-minus {
border-radius: 0 0 8px 8px;
border-radius: 0 0 8px 8px;
}
div#shotcuts > img{
margin: 20px 0;
width: 40px;
height: 40px;
div#shotcuts > img {
margin: 20px 0;
width: 40px;
height: 40px;
}
#keyboardTable {
font-family: Arial, verdana, serif;
font-size: 13px;
width: 100%;
font-family: Arial, verdana, serif;
font-size: 13px;
width: 100%;
}
#keyboardTable td {
padding: 3px;
white-space: nowrap;
padding: 3px;
white-space: nowrap;
}
#keyboardTable th {
padding: 5px;
white-space: nowrap;
padding: 5px;
white-space: nowrap;
}
#keyboardTable th {
background-color: #000000;
color: #ffffff;
background-color: #000000;
color: #ffffff;
}
.tryInfoPanel {
position: absolute;
text-align: center;
left: 0;
right: 0;
background-color: white;
border: solid 2px #ffa800;
margin: auto;
width: 99%;
border-radius: 9px;
width: 96%;
position: absolute;
text-align: center;
left: 0;
right: 0;
background-color: white;
border: solid 2px #ffa800;
margin: auto;
width: 99%;
border-radius: 9px;
width: 96%;
}
@media (min-width: 600px) {
.tryInfoPanel {
font-size: 15px;
}
.tryInfoPanel {
font-size: 15px;
}
}
@media (max-width: 600px) {
.tryInfoPanel {
font-size: 13px;
}
.tryInfoPanel {
font-size: 13px;
}
}
.tryInfoPanel .tryInfoPanelInner {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 5px;
padding-right: 5px;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 5px;
padding-right: 5px;
}
.tryInfoPanel .tryInfoPanelInner .closeButton {
position: absolute;
top: 5px;
right: 5px;
.tryInfoPanel .tryInfoPanelInner .closeButton {
position: absolute;
top: 5px;
right: 5px;
}
.tryInfoPanel .tryInfoPanelInner .closeButton button {
cursor: pointer;
border-style: hidden;
background-color: transparent;
padding: 0px;
cursor: pointer;
border-style: hidden;
background-color: transparent;
padding: 0px;
}
.tryInfoPanel .tryInfoPanelInner .closeButton button img {
width: 18px;
height: 18px;
filter: invert(73%) sepia(21%) saturate(4699%) hue-rotate(357deg) brightness(98%) contrast(108%);
width: 18px;
height: 18px;
filter: invert(73%) sepia(21%) saturate(4699%) hue-rotate(357deg) brightness(98%) contrast(108%);
}
.tryInfoPanelWithToolbar {
top: 55px;
top: 55px;
}
.tryInfoPanelWithoutToolbar {
top: 5px;
top: 5px;
}
.tryInfoPanelClosed {
display: none;
display: none;
}
.tryInfoPanel > p {
justify-content: center;
justify-content: center;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,165 +14,168 @@ import FormGroup from '@mui/material/FormGroup';
import Switch from '@mui/material/Switch';
type AccountInfoDialogProps = {
onClose: () => void;
onClose: () => void;
};
type AccountInfoModel = {
email: string;
firstname: string;
lastname: string;
email: string;
firstname: string;
lastname: string;
};
const defaultModel: AccountInfoModel = { firstname: '', lastname: '', email: '' };
const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElement => {
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const [remove, setRemove] = React.useState<boolean>(false);
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const [remove, setRemove] = React.useState<boolean>(false);
const [model, setModel] = React.useState<AccountInfoModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const [model, setModel] = React.useState<AccountInfoModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const mutationChangeName = useMutation<void, ErrorInfo, AccountInfoModel>(
(model: AccountInfoModel) => {
return client.updateAccountInfo(model.firstname, model.lastname);
},
{
onSuccess: () => {
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 => {
const mutationChangeName = useMutation<void, ErrorInfo, AccountInfoModel>(
(model: AccountInfoModel) => {
return client.updateAccountInfo(model.firstname, model.lastname);
},
{
onSuccess: () => {
queryClient.invalidateQueries('account');
onClose();
setModel(defaultModel);
setError(undefined);
};
},
onError: (error) => {
setError(error);
},
},
);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
if (remove) {
mutationRemove.mutate();
} else {
mutationChangeName.mutate(model);
}
};
const mutationRemove = useMutation<void, ErrorInfo, void>(
() => {
return client.deleteAccount();
},
{
onSuccess: () => {
window.location.href = '/c/login';
onClose();
},
onError: (error) => {
setError(error);
},
},
);
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const account = fetchAccount();
useEffect(() => {
if (account) {
setModel({
email: account?.email,
lastname: account?.lastname,
firstname: account?.firstname,
});
}
}, [account?.email]);
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof AccountInfoModel]: value });
};
const handleOnClose = (): void => {
onClose();
setModel(defaultModel);
setError(undefined);
};
const handleOnRemoveChange = (event) => {
setRemove(event.target.checked);
};
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
if (remove) {
mutationRemove.mutate();
} else {
mutationChangeName.mutate(model);
}
};
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',
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof AccountInfoModel]: value });
};
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}>
<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' })}
/>
</FormGroup>
</FormControl>
</BaseDialog>
);
/>
</FormGroup>
</FormControl>
</BaseDialog>
);
};
export default AccountInfoDialog;

View File

@ -9,108 +9,108 @@ import { useSelector } from 'react-redux';
import { activeInstance } from '../../../../redux/clientSlice';
type ChangePasswordDialogProps = {
onClose: () => void;
onClose: () => void;
};
type ChangePasswordModel = {
password: string;
retryPassword: string;
password: string;
retryPassword: string;
};
const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' };
const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps): React.ReactElement => {
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ChangePasswordModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ChangePasswordModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const mutation = useMutation<void, ErrorInfo, ChangePasswordModel>(
(model: ChangePasswordModel) => {
return client.updateAccountPassword(model.password);
},
{
onSuccess: () => {
onClose();
},
onError: (error) => {
setError(error);
},
}
);
const handleOnClose = (): void => {
const mutation = useMutation<void, ErrorInfo, ChangePasswordModel>(
(model: ChangePasswordModel) => {
return client.updateAccountPassword(model.password);
},
{
onSuccess: () => {
onClose();
setModel(defaultModel);
setError(undefined);
};
},
onError: (error) => {
setError(error);
},
},
);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
const handleOnClose = (): void => {
onClose();
setModel(defaultModel);
setError(undefined);
};
// 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 handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
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 => {
event.preventDefault();
mutation.mutate(model);
};
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ChangePasswordModel]: value });
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
return (
<BaseDialog
onClose={handleOnClose}
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"
/>
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ChangePasswordModel]: value });
};
<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>
);
return (
<BaseDialog
onClose={handleOnClose}
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
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,204 +13,206 @@ import Input from '../../../form/input';
import BaseDialog from '../base-dialog';
export type ImportModel = {
title: string;
description?: string;
contentType?: string;
content?: null | string;
title: string;
description?: string;
contentType?: string;
content?: null | string;
};
export type CreateProps = {
onClose: () => void;
onClose: () => void;
};
type ErrorFile = {
error: boolean;
message: string;
}
error: boolean;
message: string;
};
const defaultModel: ImportModel = { title: '' };
const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => {
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ImportModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const [errorFile, setErrorFile] = React.useState<ErrorFile>({error: false, message: ''});
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ImportModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const [errorFile, setErrorFile] = React.useState<ErrorFile>({ error: false, message: '' });
const intl = useIntl();
const mutation = useMutation<number, ErrorInfo, ImportModel>(
(model: ImportModel) => {
return client.importMap(model);
},
{
onSuccess: (mapId: number) => {
window.location.href = `/c/maps/${mapId}/edit`;
},
onError: (error) => {
setError(error);
},
const mutation = useMutation<number, ErrorInfo, ImportModel>(
(model: ImportModel) => {
return client.importMap(model);
},
{
onSuccess: (mapId: number) => {
window.location.href = `/c/maps/${mapId}/edit`;
},
onError: (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 => {
onClose();
setModel(defaultModel);
setError(undefined);
};
const extensionFile = file.name.split('.').pop();
const extensionAccept = ['wxml', 'mm'];
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 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);
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.',
},
),
});
}
};
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' })}
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 (
<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 &&
<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%' }}
>
<FormattedMessage
id="maps.choose-file"
defaultMessage="Choose a file"
/>
</Button>
</label>
</FormControl>
</BaseDialog>
</div>
);
<FormattedMessage id="maps.choose-file" defaultMessage="Choose a file" />
</Button>
</label>
</FormControl>
</BaseDialog>
</div>
);
};
export default ImportDialog;

View File

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

View File

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

View File

@ -12,75 +12,77 @@ import { LabelSelector } from '../../maps-list/label-selector';
import { activeInstance } from '../../../../redux/clientSlice';
import { ChangeLabelMutationFunctionParam, getChangeLabelMutationFunction } from '../../maps-list';
const LabelDialog = ({ mapsId, onClose }: MultiDialogProps): React.ReactElement => {
const intl = useIntl();
const classes = useStyles();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const intl = useIntl();
const classes = useStyles();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
// TODO: pass down map data instead of using query?
const { data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
return client.fetchAllMaps();
// TODO: pass down map data instead of using query?
const { data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
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));
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
});
};
return (
<div>
<BaseDialog
onClose={onClose}
title={intl.formatMessage({
id: 'label.title',
defaultMessage: 'Add a label',
})}
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>);
return (
<div>
<BaseDialog
onClose={onClose}
title={intl.formatMessage({
id: 'label.title',
defaultMessage: 'Add a label',
})}
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;

View File

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

View File

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

View File

@ -26,247 +26,233 @@ import RoleIcon from '../../role-icon';
import Tooltip from '@mui/material/Tooltip';
type ShareModel = {
emails: string;
role: 'editor' | 'viewer';
message: string;
emails: string;
role: 'editor' | 'viewer';
message: string;
};
const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' };
const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const classes = useStyles();
const [showMessage, setShowMessage] = React.useState<boolean>(false);
const [model, setModel] = React.useState<ShareModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const classes = useStyles();
const [showMessage, setShowMessage] = React.useState<boolean>(false);
const [model, setModel] = React.useState<ShareModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const deleteMutation = useMutation(
(email: string) => {
return client.deleteMapPermission(mapId, email);
},
{
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 ...
const deleteMutation = useMutation(
(email: string) => {
return client.deleteMapPermission(mapId, email);
},
{
onSuccess: () => {
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 => {
event.preventDefault();
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 name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ShareModel]: value });
event.stopPropagation();
};
const handleOnClose = (): void => {
// Invalidate cache ...
queryClient.invalidateQueries(`perm-${mapId}`);
onClose();
};
const handleOnAddClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.stopPropagation();
addMutation.mutate(model);
event.stopPropagation();
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const handleOnDeleteClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
email: string
): void => {
event.stopPropagation();
deleteMutation.mutate(email);
};
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ShareModel]: value });
event.stopPropagation();
};
const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(`perm-${mapId}`,
() => {
return client.fetchMapPermissions(mapId);
}
);
const handleOnAddClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.stopPropagation();
addMutation.mutate(model);
event.stopPropagation();
};
const formatName = (perm: Permission): string => {
return perm.name ? `${perm.name}<${perm.email}>` : perm.email;
};
const handleOnDeleteClick = (
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 isValid = splitEmail(model.emails)
.every(str => /\S+@\S+\.\S+/.test((str || '')
.trim()));
const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(
`perm-${mapId}`,
() => {
return client.fetchMapPermissions(mapId);
},
);
return (
<div>
<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}
/>
const formatName = (perm: Permission): string => {
return perm.name ? `${perm.name}<${perm.email}>` : perm.email;
};
<Select
variant="outlined"
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>
// very basic email validation, just make sure the basic syntax is fine
const isValid = splitEmail(model.emails).every((str) => /\S+@\S+\.\S+/.test((str || '').trim()));
<FormControlLabel
value="start"
onChange={(event, value) => {
setShowMessage(value);
}}
style={{ fontSize: '5px' }}
control={<Checkbox color="primary" />}
label={
<Typography variant="subtitle2">
<FormattedMessage
id="share.add-message"
defaultMessage="Add message"
/>
</Typography>
}
labelPlacement="end"
/>
return (
<div>
<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}
/>
<Button
color="primary"
type="button"
variant="contained"
disableElevation={true}
onClick={handleOnAddClick}
disabled={!isValid}
>
<FormattedMessage id="share.add-button" defaultMessage="Add" />
</Button>
<Select
variant="outlined"
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>
{showMessage && (
<TextField
multiline
rows={3}
maxRows={3}
className={classes.textArea}
variant="filled"
name="message"
onChange={handleOnChange}
value={model.message}
label={intl.formatMessage({
id: 'share.message',
defaultMessage: 'Message',
})}
/>
)}
</div>
<FormControlLabel
value="start"
onChange={(event, value) => {
setShowMessage(value);
}}
style={{ fontSize: '5px' }}
control={<Checkbox color="primary" />}
label={
<Typography variant="subtitle2">
<FormattedMessage id="share.add-message" defaultMessage="Add message" />
</Typography>
}
labelPlacement="end"
/>
{!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)}
/>
<Button
color="primary"
type="button"
variant="contained"
disableElevation={true}
onClick={handleOnAddClick}
disabled={!isValid}
>
<FormattedMessage id="share.add-button" defaultMessage="Add" />
</Button>
<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>
{showMessage && (
<TextField
multiline
rows={3}
maxRows={3}
className={classes.textArea}
variant="filled"
name="message"
onChange={handleOnChange}
value={model.message}
label={intl.formatMessage({
id: 'share.message',
defaultMessage: 'Message',
})}
/>
)}
</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;

View File

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

View File

@ -18,134 +18,133 @@ import DialogActions from '@mui/material/DialogActions';
import Divider from '@mui/material/Divider';
const LanguageMenu = (): React.ReactElement => {
const queryClient = useQueryClient();
const client: Client = useSelector(activeInstance);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [openHelpDialog, setHelpDialogOpen] = React.useState<boolean>(false);
const queryClient = useQueryClient();
const client: Client = useSelector(activeInstance);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [openHelpDialog, setHelpDialogOpen] = React.useState<boolean>(false);
const open = Boolean(anchorEl);
const intl = useIntl();
const open = Boolean(anchorEl);
const intl = useIntl();
const mutation = useMutation((locale: LocaleCode) => client.updateAccountLanguage(locale), {
onSuccess: () => {
queryClient.invalidateQueries('account');
const mutation = useMutation((locale: LocaleCode) => client.updateAccountLanguage(locale), {
onSuccess: () => {
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();
},
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();
setHelpDialogOpen(true);
}}
>
<FormattedMessage id="language.help" defaultMessage="Help to Translate" />
</MenuItem>
</Menu>
{openHelpDialog && <HelpUsToTranslateDialog onClose={() => setHelpDialogOpen(false)} />}
</span>
);
setHelpDialogOpen(true);
}}
>
<FormattedMessage id="language.help" defaultMessage="Help to Translate" />
</MenuItem>
</Menu>
{openHelpDialog && <HelpUsToTranslateDialog onClose={() => setHelpDialogOpen(false)} />}
</span>
);
};
type HelpUsToTranslateDialogProp = {
onClose: () => void;
onClose: () => void;
};
const HelpUsToTranslateDialog = ({ onClose }: HelpUsToTranslateDialogProp) => {
return (
<Dialog open={true} onClose={onClose}>
<DialogTitle>Help us to support more languages !</DialogTitle>
<DialogContent>
<DialogContentText>
We need your help !. If you are interested, send us an email to
team@wisemapping.com.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={onClose}>
Close
</Button>
</DialogActions>
</Dialog>
);
return (
<Dialog open={true} onClose={onClose}>
<DialogTitle>Help us to support more languages !</DialogTitle>
<DialogContent>
<DialogContentText>
We need your help !. If you are interested, send us an email to team@wisemapping.com.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={onClose}>
Close
</Button>
</DialogActions>
</Dialog>
);
};
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';
export type LabelDeleteConfirmType = {
label: Label;
onClose: () => void;
onConfirm: () => void;
label: Label;
onClose: () => void;
onConfirm: () => void;
};
const LabelDeleteConfirm = ({ label, onClose, onConfirm }: LabelDeleteConfirmType): React.ReactElement => {
const intl = useIntl();
const LabelDeleteConfirm = ({
label,
onClose,
onConfirm,
}: LabelDeleteConfirmType): React.ReactElement => {
const intl = useIntl();
return (
<div>
<BaseDialog
onClose={onClose}
onSubmit={onConfirm}
title={intl.formatMessage({ id: 'label.delete-title', defaultMessage: 'Confirm label deletion' })}
submitButton={intl.formatMessage({
id: 'action.delete-title',
defaultMessage: 'Delete',
})}
>
<Alert severity="warning">
<AlertTitle>{intl.formatMessage({ id: 'label.delete-title', defaultMessage: 'Confirm label deletion' })}</AlertTitle>
<span>
<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>
);
return (
<div>
<BaseDialog
onClose={onClose}
onSubmit={onConfirm}
title={intl.formatMessage({
id: 'label.delete-title',
defaultMessage: 'Confirm label deletion',
})}
submitButton={intl.formatMessage({
id: 'action.delete-title',
defaultMessage: 'Delete',
})}
>
<Alert severity="warning">
<AlertTitle>
{intl.formatMessage({
id: 'label.delete-title',
defaultMessage: 'Confirm label deletion',
})}
</AlertTitle>
<span>
<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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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