First commit waiting for Budget Alert

This commit is contained in:
2025-09-04 13:37:35 +01:00
commit 2d681f27f5
4563 changed files with 1061534 additions and 0 deletions

View File

@ -0,0 +1,71 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Master CSS file.
*/
@import url("../lib/open-sans/css/open-sans.css");
@import url("../lib/jquery.ui/css/smoothness/jquery-ui.css");
@import url("../lib/jquery.timepicker/jquery-ui-timepicker-addon.css");
@import url("../lib/bootstrap/css/bootstrap.css");
@import url("../lib/bootstrap/css/bootstrap-responsive.css");
@import url("../lib/font-awesome/css/font-awesome.css");
@import url("../lib/slickgrid/slick.grid.css");
@import url("../lib/slickgrid/plugins/slick.headermenu.css");
@import url("../lib/jquery.treetable/css/jquery.treetable.css");
@import url("../lib/jquery.treetable/css/jquery.treetable.theme.default.css");
@import url("../lib/fullcalendar/fullcalendar.css");
@import url("../lib/fullcalendar/fullcalendar.print.css") print;
@import url("../lib/dhtmlxGantt/dhtmlxgantt.css");
@import url("../lib/handsontable/handsontable.full.css");
@import url("../lib/d3/nv/nv.d3.css");
@import url("../lib/codemirror/codemirror.min.css");
@import url("../lib/wysiwyg/wysiwyg-editor.css");
@import url("wysiwyg.css");
@import url("main.css");
@import url("tabs.css");
@import url("navtree.css");
@import url("slickgrid.css");
@import url("view.form.css");
@import url("view.form.panels.css");
@import url("view.form.checkbox.css");
@import url("view.popup.css");
@import url("view.tree.css");
@import url("view.portal.css");
@import url("view.calendar.css");
@import url("view.gantt.css");
@import url("view.kanban.css");
@import url("view.mail.css");
@import url("view.dms.css");
@import url("view.form.hilite.css");
@import url("colors.css");
@import url("print.css") print;

View File

@ -0,0 +1,27 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Login CSS file.
*/
@import url("../lib/bootstrap/css/bootstrap.css");
@import url("../lib/bootstrap/css/bootstrap-responsive.css");
@import url("../lib/font-awesome/css/font-awesome.css");
@import url("view.form.checkbox.css");
@import url("colors.css");
@import url("login.css");

View File

@ -0,0 +1,478 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@import url('../blue-simple/theme.css');
[data-name="menu-admin"] .nav-icon {
background-color: #E03997;
}
.panel, .dashlet {
border: 1px solid #2185D0;
}
.panel .panel-header,
.dashlet .dashlet-header {
padding: 5px 10px 7px !important;
border-top-color: #2185D0;
border-bottom-color: #2185D0;
background-color: #2185D0;
}
.dashlet .dashlet-body {
border-top-color: #2185D0;
}
.report-box .report-tags {
top: 6px;
right: 6px;
}
.panel .panel-title, .dashlet .dashlet-title,
.dashboard .dashlet-pager, .dashboard .dashlet-buttons > a, .dashboard .dashlet-pager > a {
color: #F9FAFB;
}
.dashboard .dashlet-pager > a.disabled {
color: #F9FAFB;
opacity: .65;
}
.panel-red .panel,
.panel-red .dashlet {
border-color: #DB2828;
}
.panel-red .panel .panel-header,
.panel-red .dashlet .dashlet-header {
border-top-color: #DB2828;
border-bottom-color: #DB2828;
background-color: #DB2828;
}
.panel-red .dashlet .dashlet-body {
border-top-color: #DB2828;
}
.panel-red.panel {
border-color: #DB2828 !important;
}
.panel-red.panel .panel-header {
border-top-color: #DB2828 !important;
border-bottom-color: #DB2828 !important;
background-color: #DB2828 !important;
}
.panel-red.dashlet {
border-color: #DB2828 !important;
}
.panel-red.dashlet .dashlet-header {
border-top-color: #DB2828 !important;
border-bottom-color: #DB2828 !important;
background-color: #DB2828 !important;
}
.panel-red.dashlet .dashlet-body {
border-top-color: #DB2828 !important;
}
.panel-orange .panel,
.panel-orange .dashlet {
border-color: #F2711C;
}
.panel-orange .panel .panel-header,
.panel-orange .dashlet .dashlet-header {
border-top-color: #F2711C;
border-bottom-color: #F2711C;
background-color: #F2711C;
}
.panel-orange .dashlet .dashlet-body {
border-top-color: #F2711C;
}
.panel-orange.panel {
border-color: #F2711C !important;
}
.panel-orange.panel .panel-header {
border-top-color: #F2711C !important;
border-bottom-color: #F2711C !important;
background-color: #F2711C !important;
}
.panel-orange.dashlet {
border-color: #F2711C !important;
}
.panel-orange.dashlet .dashlet-header {
border-top-color: #F2711C !important;
border-bottom-color: #F2711C !important;
background-color: #F2711C !important;
}
.panel-orange.dashlet .dashlet-body {
border-top-color: #F2711C !important;
}
.panel-yellow .panel,
.panel-yellow .dashlet {
border-color: #FBBD08;
}
.panel-yellow .panel .panel-header,
.panel-yellow .dashlet .dashlet-header {
border-top-color: #FBBD08;
border-bottom-color: #FBBD08;
background-color: #FBBD08;
}
.panel-yellow .dashlet .dashlet-body {
border-top-color: #FBBD08;
}
.panel-yellow.panel {
border-color: #FBBD08 !important;
}
.panel-yellow.panel .panel-header {
border-top-color: #FBBD08 !important;
border-bottom-color: #FBBD08 !important;
background-color: #FBBD08 !important;
}
.panel-yellow.dashlet {
border-color: #FBBD08 !important;
}
.panel-yellow.dashlet .dashlet-header {
border-top-color: #FBBD08 !important;
border-bottom-color: #FBBD08 !important;
background-color: #FBBD08 !important;
}
.panel-yellow.dashlet .dashlet-body {
border-top-color: #FBBD08 !important;
}
.panel-olive .panel,
.panel-olive .dashlet {
border-color: #B5CC18;
}
.panel-olive .panel .panel-header,
.panel-olive .dashlet .dashlet-header {
border-top-color: #B5CC18;
border-bottom-color: #B5CC18;
background-color: #B5CC18;
}
.panel-olive .dashlet .dashlet-body {
border-top-color: #B5CC18;
}
.panel-olive.panel {
border-color: #B5CC18 !important;
}
.panel-olive.panel .panel-header {
border-top-color: #B5CC18 !important;
border-bottom-color: #B5CC18 !important;
background-color: #B5CC18 !important;
}
.panel-olive.dashlet {
border-color: #B5CC18 !important;
}
.panel-olive.dashlet .dashlet-header {
border-top-color: #B5CC18 !important;
border-bottom-color: #B5CC18 !important;
background-color: #B5CC18 !important;
}
.panel-olive.dashlet .dashlet-body {
border-top-color: #B5CC18 !important;
}
.panel-green .panel,
.panel-green .dashlet {
border-color: #21BA45;
}
.panel-green .panel .panel-header,
.panel-green .dashlet .dashlet-header {
border-top-color: #21BA45;
border-bottom-color: #21BA45;
background-color: #21BA45;
}
.panel-green .dashlet .dashlet-body {
border-top-color: #21BA45;
}
.panel-green.panel {
border-color: #21BA45 !important;
}
.panel-green.panel .panel-header {
border-top-color: #21BA45 !important;
border-bottom-color: #21BA45 !important;
background-color: #21BA45 !important;
}
.panel-green.dashlet {
border-color: #21BA45 !important;
}
.panel-green.dashlet .dashlet-header {
border-top-color: #21BA45 !important;
border-bottom-color: #21BA45 !important;
background-color: #21BA45 !important;
}
.panel-green.dashlet .dashlet-body {
border-top-color: #21BA45 !important;
}
.panel-teal .panel,
.panel-teal .dashlet {
border-color: #00B5AD;
}
.panel-teal .panel .panel-header,
.panel-teal .dashlet .dashlet-header {
border-top-color: #00B5AD;
border-bottom-color: #00B5AD;
background-color: #00B5AD;
}
.panel-teal .dashlet .dashlet-body {
border-top-color: #00B5AD;
}
.panel-teal.panel {
border-color: #00B5AD !important;
}
.panel-teal.panel .panel-header {
border-top-color: #00B5AD !important;
border-bottom-color: #00B5AD !important;
background-color: #00B5AD !important;
}
.panel-teal.dashlet {
border-color: #00B5AD !important;
}
.panel-teal.dashlet .dashlet-header {
border-top-color: #00B5AD !important;
border-bottom-color: #00B5AD !important;
background-color: #00B5AD !important;
}
.panel-teal.dashlet .dashlet-body {
border-top-color: #00B5AD !important;
}
.panel-blue .panel,
.panel-blue .dashlet {
border-color: #2185D0;
}
.panel-blue .panel .panel-header,
.panel-blue .dashlet .dashlet-header {
border-top-color: #2185D0;
border-bottom-color: #2185D0;
background-color: #2185D0;
}
.panel-blue .dashlet .dashlet-body {
border-top-color: #2185D0;
}
.panel-blue.panel {
border-color: #2185D0 !important;
}
.panel-blue.panel .panel-header {
border-top-color: #2185D0 !important;
border-bottom-color: #2185D0 !important;
background-color: #2185D0 !important;
}
.panel-blue.dashlet {
border-color: #2185D0 !important;
}
.panel-blue.dashlet .dashlet-header {
border-top-color: #2185D0 !important;
border-bottom-color: #2185D0 !important;
background-color: #2185D0 !important;
}
.panel-blue.dashlet .dashlet-body {
border-top-color: #2185D0 !important;
}
.panel-violet .panel,
.panel-violet .dashlet {
border-color: #6435C9;
}
.panel-violet .panel .panel-header,
.panel-violet .dashlet .dashlet-header {
border-top-color: #6435C9;
border-bottom-color: #6435C9;
background-color: #6435C9;
}
.panel-violet .dashlet .dashlet-body {
border-top-color: #6435C9;
}
.panel-violet.panel {
border-color: #6435C9 !important;
}
.panel-violet.panel .panel-header {
border-top-color: #6435C9 !important;
border-bottom-color: #6435C9 !important;
background-color: #6435C9 !important;
}
.panel-violet.dashlet {
border-color: #6435C9 !important;
}
.panel-violet.dashlet .dashlet-header {
border-top-color: #6435C9 !important;
border-bottom-color: #6435C9 !important;
background-color: #6435C9 !important;
}
.panel-violet.dashlet .dashlet-body {
border-top-color: #6435C9 !important;
}
.panel-purple .panel,
.panel-purple .dashlet {
border-color: #A333C8;
}
.panel-purple .panel .panel-header,
.panel-purple .dashlet .dashlet-header {
border-top-color: #A333C8;
border-bottom-color: #A333C8;
background-color: #A333C8;
}
.panel-purple .dashlet .dashlet-body {
border-top-color: #A333C8;
}
.panel-purple.panel {
border-color: #A333C8 !important;
}
.panel-purple.panel .panel-header {
border-top-color: #A333C8 !important;
border-bottom-color: #A333C8 !important;
background-color: #A333C8 !important;
}
.panel-purple.dashlet {
border-color: #A333C8 !important;
}
.panel-purple.dashlet .dashlet-header {
border-top-color: #A333C8 !important;
border-bottom-color: #A333C8 !important;
background-color: #A333C8 !important;
}
.panel-purple.dashlet .dashlet-body {
border-top-color: #A333C8 !important;
}
.panel-pink .panel,
.panel-pink .dashlet {
border-color: #E03997;
}
.panel-pink .panel .panel-header,
.panel-pink .dashlet .dashlet-header {
border-top-color: #E03997;
border-bottom-color: #E03997;
background-color: #E03997;
}
.panel-pink .dashlet .dashlet-body {
border-top-color: #E03997;
}
.panel-pink.panel {
border-color: #E03997 !important;
}
.panel-pink.panel .panel-header {
border-top-color: #E03997 !important;
border-bottom-color: #E03997 !important;
background-color: #E03997 !important;
}
.panel-pink.dashlet {
border-color: #E03997 !important;
}
.panel-pink.dashlet .dashlet-header {
border-top-color: #E03997 !important;
border-bottom-color: #E03997 !important;
background-color: #E03997 !important;
}
.panel-pink.dashlet .dashlet-body {
border-top-color: #E03997 !important;
}
.panel-brown .panel,
.panel-brown .dashlet {
border-color: #A5673F;
}
.panel-brown .panel .panel-header,
.panel-brown .dashlet .dashlet-header {
border-top-color: #A5673F;
border-bottom-color: #A5673F;
background-color: #A5673F;
}
.panel-brown .dashlet .dashlet-body {
border-top-color: #A5673F;
}
.panel-brown.panel {
border-color: #A5673F !important;
}
.panel-brown.panel .panel-header {
border-top-color: #A5673F !important;
border-bottom-color: #A5673F !important;
background-color: #A5673F !important;
}
.panel-brown.dashlet {
border-color: #A5673F !important;
}
.panel-brown.dashlet .dashlet-header {
border-top-color: #A5673F !important;
border-bottom-color: #A5673F !important;
background-color: #A5673F !important;
}
.panel-brown.dashlet .dashlet-body {
border-top-color: #A5673F !important;
}
.panel-grey .panel,
.panel-grey .dashlet {
border-color: #767676;
}
.panel-grey .panel .panel-header,
.panel-grey .dashlet .dashlet-header {
border-top-color: #767676;
border-bottom-color: #767676;
background-color: #767676;
}
.panel-grey .dashlet .dashlet-body {
border-top-color: #767676;
}
.panel-grey.panel {
border-color: #767676 !important;
}
.panel-grey.panel .panel-header {
border-top-color: #767676 !important;
border-bottom-color: #767676 !important;
background-color: #767676 !important;
}
.panel-grey.dashlet {
border-color: #767676 !important;
}
.panel-grey.dashlet .dashlet-header {
border-top-color: #767676 !important;
border-bottom-color: #767676 !important;
background-color: #767676 !important;
}
.panel-grey.dashlet .dashlet-body {
border-top-color: #767676 !important;
}
.panel-black .panel,
.panel-black .dashlet {
border-color: #1B1C1D;
}
.panel-black .panel .panel-header,
.panel-black .dashlet .dashlet-header {
border-top-color: #1B1C1D;
border-bottom-color: #1B1C1D;
background-color: #1B1C1D;
}
.panel-black .dashlet .dashlet-body {
border-top-color: #1B1C1D;
}
.panel-black.panel {
border-color: #1B1C1D !important;
}
.panel-black.panel .panel-header {
border-top-color: #1B1C1D !important;
border-bottom-color: #1B1C1D !important;
background-color: #1B1C1D !important;
}
.panel-black.dashlet {
border-color: #1B1C1D !important;
}
.panel-black.dashlet .dashlet-header {
border-top-color: #1B1C1D !important;
border-bottom-color: #1B1C1D !important;
background-color: #1B1C1D !important;
}
.panel-black.dashlet .dashlet-body {
border-top-color: #1B1C1D !important;
}
.file-item .file-item {
display: block !important;
}
.file-item button.btn.ng-hide:first-child{
display: block !important;
pointer-events: none;
}
button.btn.ng-hide .fa-arrow-circle-up:before{
content: "\f0c6" !important;
}

View File

@ -0,0 +1,111 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
header .navbar .nav > li > a:focus,
header .navbar .nav > li > a:hover,
header .navbar .nav > li.dropdown.open > a.dropdown-toggle {
color: #2185D0;
}
header .navbar .nav .dropdown-menu > li > a:hover,
header .navbar .nav .dropdown-menu > li > a:focus,
header .navbar .nav .dropdown-submenu:hover > a,
header .navbar .nav .dropdown-submenu:focus > a {
background-color: #2185D0;
}
header .navbar .nav > li > a:focus,
header .navbar .nav > li > a:hover,
header .navbar .nav > li.dropdown.open > a.dropdown-toggle {
color: #2185D0;
}
textarea:focus, input[type="text"]:focus,
input[type="password"]:focus,
input[type="datetime"]:focus,
input[type="datetime-local"]:focus,
input[type="date"]:focus,
input[type="month"]:focus,
input[type="time"]:focus,
input[type="week"]:focus,
input[type="number"]:focus,
input[type="email"]:focus,
input[type="url"]:focus,
input[type="search"]:focus,
input[type="tel"]:focus,
input[type="color"]:focus,
.uneditable-input:focus {
border-color: #2185D0;
}
.ui-datepicker .ui-slider-handle.ui-state-focus,
.ui-datepicker .ui-slider-handle.ui-state-hover,
.ui-datepicker .ui-state-highlight {
border-color: #2185D0 !important;
}
.ui-datepicker .ui-state-active,
.ui-datepicker .ui-slider-handle.ui-state-hover {
background-color: #2185D0 !important;
}
/* navs */
.nav-tree > li.open {
border-left-color: #2185D0;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
border-top-color: #2185D0 !important;
}
/* selects */
.tag-select:focus,
.tag-select.focus {
border-color: #2185D0;
}
.nav-select li.active a.nav-label,
.nav-select li.active a.nav-label:after {
background: #2185D0;
}
/* boxes */
.ibox input:focus + .box:before {
border-color: #2185D0;
}
.ibox:hover .box:before,
.ibox:hover input:focus + .box:before {
border-color: #2185D0;
}
.ibox input:checked + .box:before {
border-color: #2185D0;
background-color: #2185D0;
}
.ibox:hover input:disabled:checked + .box:before {
border-color: 1px solid #2185D0;
}
.iswitch {
background-color: #2185D0;
}
/* panels */
.panel .panel-header,
.dashlet .dashlet-header {
border-top-width: 2px;
}

356
sophal/css/colors.css Normal file
View File

@ -0,0 +1,356 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
header {
font-weight: 600;
}
header .navbar-inner {
background-color: #fff;
box-shadow: none !important;
}
header .navbar .nav .dropdown-menu {
border: 1px solid #d3d3d3;
border-top: 1px solid #e7e7e7;
}
header .navbar .nav .dropdown-submenu > .dropdown-menu {
margin-left: 0;
border-top: 1px solid #d3d3d3;
}
header .navbar .nav .dropdown-menu > li > a {
padding: 6px 25px 6px 20px;
line-height: 20px;
}
header .navbar .nav .dropdown-submenu > a:after {
margin-right: -15px;
}
header .navbar .nav .dropdown-submenu:hover > a:after {
border-left-color: #fff;
}
header .navbar .nav .fa-bars,
header .navbar .dropdown-toggle .caret {
color: #3ad08f;
}
.navbar .nav li.dropdown.open > .dropdown-toggle,
.navbar .nav li.dropdown.active > .dropdown-toggle,
.navbar .nav li.dropdown.open.active > .dropdown-toggle {
color: #107a4a;
}
header .navbar .nav > li > a:focus,
header .navbar .nav > li > a:hover {
color: #3ad08f;
}
header .navbar .nav .fa-bars:hover,
header .navbar .nav .fa-bars:focus {
color: #107a4a;
}
header .dropdown-menu > li > a:hover,
header .dropdown-menu > li > a:focus,
header .dropdown-submenu:hover > a,
header .dropdown-submenu:focus > a {
color: #fff;
background-color: #3ad08f;
}
.slick-header-menuitem:hover {
color: #fff;
background-color: #5377b1;
background-image: none;
}
.ui-datepicker .ui-state-active,
.ui-datepicker .ui-slider-handle.ui-state-hover {
color: #fff !important;
background-color: #0275d8 !important;
background-image: none !important;
}
.ui-datepicker .ui-slider-handle.ui-state-focus,
.ui-datepicker .ui-slider-handle.ui-state-hover,
.ui-datepicker .ui-state-highlight {
border: 1px solid #0275d8 !important
}
.ui-menu .ui-menu-item a.ui-state-focus {
color: #262626;
text-decoration: none;
border-color: #f5f5f5;
background-color: #f5f5f5;
background-image: none;
}
.sidebar {
background-color: #2f4050;
}
.view-tabs {
background-color: #f3f3f4;
}
.nav-tabs-wrap {
height: 38px;
background-color: #fff;
border-bottom: 1px solid #e7e7e7;
}
.nav-tabs-wrap.empty {
display: none;
}
.nav-tabs > li {
margin-right: -1px;
margin-bottom: -1px;
}
.nav-tabs > li > a {
color: #555;
background-color: #fff;
padding-top: 6px;
padding-bottom: 6px;
}
.nav-tabs > li.active > a {
color: #333;
}
.nav-tabs > li > a,
.nav-tabs > li.active > a {
margin: 0;
border-radius: 0;
border: 1px solid #e7e7e7;
}
.nav-tabs > li > a:hover,
.nav-tabs > li > a:focus,
.nav-tabs > li:hover > a {
color: #333;
border: 1px solid #e7e7e7;
background-color: #fff;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
border: 1px solid #e7e7e7;
border-top: 3px solid #0275d8;
border-bottom: 1px solid transparent;
background-color: #fff;
padding-top: 4px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.nav-tabs-main > li:not(.active) > a {
border-top-color: #e7e7e7 !important;
}
.nav-tabs-main > li:not(.active) > a > i.fa {
opacity: 0.55;
}
.nav-tabs-strip {
height: 32px;
background-color: #f3f3f4;
}
.nav-tabs-strip .nav-tabs {
padding: 0 8px;
border-bottom-color: #e7e7e7;
}
.nav-tabs-overflow .nav-tabs {
padding: 0;
}
.nav-tabs-scroll-l,
.nav-tabs-scroll-r {
bottom: 6px;
margin-top: 0;
padding-top: 8px;
background-color: #f3f3f4;
border-bottom: 1px solid #e7e7e7;
}
.nav-tabs-scroll-l a,
.nav-tabs-scroll-r a {
width: 100%;
padding-left: 3px;
}
table.form-layout .tabbable-tabs .tab-content {
border-color: #e7e7e7;
}
.bordered,
.bordered-box {
border-color: #e7e7e7;
}
.view-container .form-view > div[ui-view-form].has-width {
background-color: #f3f3f4;
}
/** remove corners */
.btn,
.ui-menu,
.dropdown-menu,
.slick-header-menu,
[class*="ui-corner"] {
border-radius: 0 !important;
}
.portlet,
.portlet-header .navbar-inner,
.portlet-body,
.portlet-content {
border-radius: 0 !important;
}
input, button, textarea, select, fieldset, pre {
border-radius: 0 !important;
}
.tag-select,
.tag-select .label {
border-radius: 0;
}
.tag-select .picker-icons > i.fa {
padding-top: 3px;
}
.tag-select .label {
background-color: #0275d8;
}
.select-item .picker-icons i.fa-times {
font-size: 12px;
color: #ccc;
}
.select-item .picker-icons i.fa-times:hover {
color: #333;
}
.m2o-editor-controls i.fa,
.picker-icons i.fa {
color: #ccc;
}
.m2o-editor-controls i.fa:hover,
.picker-icons i.fa:hover {
color: #333;
}
.progress,
.progress .bar {
border-radius: 0 !important;
box-shadow: none;
}
.nvd3.nv-noData {
font-weight: 400;
fill: #333333;
}
/* jquery fixes */
.ui-widget-header a {
color: #0088cc;
}
.ui-dialog .ui-dialog-titlebar a {
color: #222;
}
/* == v5 == */
.record-toolbar button,
.record-toolbar .btn {
border: 0;
}
.record-toolbar .button-menu.open a.btn,
.record-toolbar .button-menu a.btn:hover {
background-color: #eee;
}
.tag-select {
border-top-width: 0;
border-left-width: 0;
border-right-width: 0;
padding-left: 0;
}
.tag-select > .picker-icons {
right: 0;
}
.nav-tabs-main > li.active > a.bg-red { border-top-color: #DB2828 !important; }
.nav-tabs-main > li.active > a.bg-orange { border-top-color: #F2711C !important; }
.nav-tabs-main > li.active > a.bg-yellow { border-top-color: #FBBD08 !important; }
.nav-tabs-main > li.active > a.bg-olive { border-top-color: #B5CC18 !important; }
.nav-tabs-main > li.active > a.bg-green { border-top-color: #21BA45 !important; }
.nav-tabs-main > li.active > a.bg-teal { border-top-color: #00B5AD !important; }
.nav-tabs-main > li.active > a.bg-blue { border-top-color: #2185D0 !important; }
.nav-tabs-main > li.active > a.bg-violet { border-top-color: #6435C9 !important; }
.nav-tabs-main > li.active > a.bg-purple { border-top-color: #A333C8 !important; }
.nav-tabs-main > li.active > a.bg-pink { border-top-color: #E03997 !important; }
.nav-tabs-main > li.active > a.bg-brown { border-top-color: #A5673F !important; }
.nav-tabs-main > li.active > a.bg-grey { border-top-color: #767676 !important; }
.nav-tabs-main > li.active > a.bg-black { border-top-color: #1B1C1D !important; }
.nav-tabs-main > li.active > a.bg-white { border-top-color: #F9FAFB !important; }
.panel .panel-related .panel-header,
.panel-tabs .panel-related .panel-header,
.panel .dashboard .dashlet-header,
.panel-tabs .dashboard .dashlet-header,
.panel .one2many-item > .navbar .navbar-inner,
.panel-tabs .one2many-item > .navbar .navbar-inner,
.panel .many2many-item > .navbar .navbar-inner,
.panel-tabs .many2many-item > .navbar .navbar-inner {
border: 0;
padding: 8px 2px;
}
.panel .one2many-item > .slickgrid,
.panel-tabs .one2many-item > .slickgrid,
.panel .many2many-item > .slickgrid,
.panel-tabs .many2many-item > .slickgrid {
top: 34px;
}
.panel .panel-related,
.panel-tabs .panel-related,
.panel .form-item .dashlet,
.panel-tabs .form-item .dashlet {
border: 0;
box-shadow: none;
}
.panel .panel-related .panel-body .slickgrid,
.panel-tabs .panel-related .panel-body .slickgrid,
.panel .form-item .dashlet .dashlet-body .portlet-grid .slickgrid,
.panel-tabs .form-item .dashlet .dashlet-body .portlet-grid .slickgrid {
border: 1px solid #ddd;
height: calc(100% - 2px);
}
.panel-related .panel-header .icons-bar > a,
.one2many-item .navbar .icons-bar > a,
.many2many-item .navbar .icons-bar > a {
margin-left: 16px;
}
/* == v5 == */

592
sophal/css/core.css Normal file
View File

@ -0,0 +1,592 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
********************************************* View content (default background image) **************************************************************
*/
.view-container .form-view > div[ui-view-form].has-width {
background: url(../public/img/background.gif);
}
.view-container .form-view > div[ui-view-form].has-width > form{
width: 95%;
}
/************************************ Table Layout ******************************************************************************/
table.form-layout td.form-label > label{
float:left;
text-align: left;
}
table.form-layout td.form-item .separator-item > span {
font-weight: bold;
}
/************************************** Header Layout **************************************************************************/
header .navbar-inverse .navbar-inner {
background-image: linear-gradient(to bottom, #646060, #262626);
background-image: -moz-linear-gradient(to bottom, #646060, #262626);
background-image: linear-gradient(to bottom, #646060, #262626);
border-bottom: 1px solid #262626;
filter: none; /*IE*/
}
header .container-fluid {
padding-right: 20px;
padding-left: 30px;
}
.dropdown-menu > li > a{
/* color: white !important; */
color: white;
white-space: normal;
}
.navbar .navbar-inner .container-fluid * > img{
padding-right: 5px;
}
header .navbar-inverse .dropdown-menu{
margin-right: -6px !important;
margin-left: -4px !important;
}
.container > img{
padding-top: 5px;
}
.calendar-view .record-pager > span{
margin-right: 10px;
margin-top: 2px !important;
}
header .navbar-inverse .dropdown-menu .divider{
border-bottom: 1px solid white !important;
border-top: 1px solid white !important;
}
.dropdown-toggle {
margin-left: -4px;
margin-right: -5px;
}
.pull-right li:nth-child(2) a:before{
content:"\f015";
padding:5px;
font-family:FontAwesome;
font-size:20px;
}
.pull-right li:nth-child(4) a.dropdown-toggle:before{
content:"\f05a";
padding:5px;
font-family:FontAwesome;
font-size:20px;
}
.pull-right li:nth-child(6) a.dropdown-toggle:before{
content:"\f007";
padding:5px 5px 8px 2px;
font-family:FontAwesome;
font-size:18px;
}
.pull-right li:nth-child(4) img,.pull-right li:nth-child(2) img,.pull-right li:nth-child(6) img{
display:none;
}
.navbar .nav > li > .dropdown-menu.pull-right{
right: 1px;
}
.pull-right li:nth-child(6) .dropdown-menu{
right:1px !important;
}
header .navbar-inverse .dropdown-menu > li > a:hover{
background-color: #E0DFDF !important;
color : #333333 !important;
font-style: none;
}
header .navbar-inverse .dropdown-menu,
header .navbar-inverse .dropdown-submenu:hover > a,
header .navbar-inverse .dropdown-submenu:focus > a {
background-color: #646060 !important;
border :none;
margin-left: 5px !important;
}
.dropdown-submenu > .dropdown-menu{
left : 97%;
}
.navbar .nav > li > a {
margin-left: 5px;
}
.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
background-color: #646060;
color: white !important;
}
/************************************************* Menu Layout **************************************************************************/
header .navbar-inverse .dropdown-menu > li > a{
color: white;
}
.splitter-vertical{
background-image: linear-gradient(to left, #646060, #262626);
background-image: linear-gradient(to left, #646060, #262626);
background-image: llinear-gradient(to left, #646060, #262626);
padding-right: -5px;
width:5px !important;
height: auto;
/* left: 210px !important; */
}
.splitter-vertical:before{
content: ".\a.\a.\a.\a.\a.\a.";
color: white !important;
position: absolute;
top: 50%;
line-height: 5px;
left: 0px;
font-size: 18px;
}
.filter-menu {
margin-left: 8px;
background: #f1f1f1;
top: 40px !important;
border-radius : 6px !important;
}
.filter-menu:before {
/* content: "\f0d8"; */
font-family: FontAwesome;
position: absolute;
top: -12px;
left: 50.8%;
color: #f1f1f1;
font-size: 20px;
}
.filter-menu form{
border-top: 1px solid #dddddd;
}
.nav-list > .active * > .icon-caret-down:before,
.nav-list > .active * > .icon-caret-right:before{
color: white !important;
}
.splitter-footer{
margin-bottom: 5px;
padding-top: 3px;
}
.nav-list > .active * > .icon-caret-down:before{
color: white !important;
}
.input-append input, .input-append select, .input-append .uneditable-input{
padding-bottom : 7px;
}
.record-pager-text{
margin-top: 6px;
margin-right: 12px;
}
.stackbar .navbar,
.stackbar .navbar * {
color: white;
}
.nav > li > a > img{
margin-bottom: 3px;
}
.navbar .navbar-inner .container-fluid * > img{
float:left !important;
}
.ui-dialog .ui-dialog-titlebar {
border : none;
border-bottom: 1px solid #B2C3D6;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background: none;
padding: 9px 15px;
font-size: 24.5px;
line-height: 30px;
color: black !important;
}
.ui-dialog .ui-dialog-buttonpane {
text-align: left;
border : none;
border-top: 1px solid #B2C3D6;left: 210px
background-image: none;
margin: .5em 0 0 0;
padding: .3em 1em .5em .4em;
}
.nav-tabs > li > a, .nav-pills > li > a {
padding-right: 12px;
padding-left: 12px;
margin-right: 0px;
line-height: 14px;
}
.navbar .brand{
text-shadow: none;
font-weight: normal;
}
.stackbar .navbar * > .icon-plus:before,
.stackbar .navbar * > .icon-refresh:before,
.stackbar .navbar * > .icon-pencil:before,
.stackbar .navbar * > .icon-minus:before,
.stackbar .navbar * > .icon-search:before,
.navbar .nav li.dropdown > .dropdown-toggle .caret,
.nav-tabs .inactive:hover > .icon-remove:before,
.nav-tabs-wrap .nav-tabs .active > .icon-remove:before,
.icon-step-backward:before,
.icon-step-forward:before,
.icon-chevron-down:before,
.icon-chevron-up:before{
color: white !important ;
}
/* .icon-remove:before{ */
/* color: white; */
/* } */
.nav-tabs-strip .nav-tabs {
height: 30px;
}
.nav-tabs-strip {
height: 30px;
}
.nav-tabs-strip > .nav.nav-tabs.nav-tabs-scrollable.nav-tabs-closable > .ng-scope.active,
.nav-tabs-strip > .nav.nav-tabs.nav-tabs-scrollable.nav-tabs-closable > .ng-scope.inactive {
height: 30px;
}
.nav-tabs-strip > .nav.nav-tabs.nav-tabs-scrollable.nav-tabs-closable > .ng-scope.active > a,
.nav-tabs-strip > .nav.nav-tabs.nav-tabs-scrollable.nav-tabs-closable > .ng-scope.inactive > a {
height: 13px;
margin:1px;
}
.nav-tabs-closable li > i {
top: 3px;
right: 3px;
}
/**
**************************************** Button Layout ***************************************************************************
*/
a.button-item.img-button {
width: 100% !important;
}
.navbar .container{
padding-right: 10px;
}
.navbar .btn, .navbar .btn-group{
margin-right: -2px;
margin-bottom: 2px;
}
a.button-item.img-button img {
height: 64px;
}
.icon-table:before{
content: "\f0ca";
}
.icon-edit:before{
content: "\f0f6";
}
.btn-group * > .icon-eye-open:before{
content: "\f02b";
}
.icon-eye-open:before{
content: "\f06e";
}
.btn-group * > .icon-remove:before {
content: "\f014";
/* color : black; */
}
.btn-group * >.icon-file:before {
content: "\f067";
}
/* .open > .dropdown-menu > li > a > .icon-off:before, .open > .dropdown-menu > li > a > .icon-cog:before{ */
/* color:white !important; */
/* } */
.btn-group > .btn > img{
padding-right: 7px;
}
.btn-group > .btn + .btn{
margin-left: 1px;
margin-right: -2px;
margin-top: 0px;
}
.record-pager.pull-right > span, .record-pager.pull-left > .btn-group{
margin-top: 2px;
}
.btn-text{
margin-left: 4px;
}
.record-toolbar .dropdown a.dropdown-toggle{
margin-top: 4px;
padding: 4px 8px 5px;
}
.btn-group{
margin-top:2px !important;
}
.pull-right {
margin-top: 2px;
}
/**
******************************************************* SN *********************************************************
*/
.sn-buttons {
height: 30px !important;
width: 50px !important;
border: none !important;
display: inline-block;
}
.sn-buttons img {
height: 25px;
}
.sn-comment-icons {
top: -3px;
}
/**
* Trail
*/
.trail-form {
background-color: #fff;
border-style: solid;
border-width: 1px;
border-color: #a2a2a2;
}
.trail-form.trail-child {
margin-left: 60px;
width: 592px !important;
background-color: #eee;
border-style: none;
}
.trail-expand-button {
bottom: 1px !important;
right: 1px;
}
.nav-tabs-closable{
height: 38px !important;
}
.nav-tabs-closable* > a > img{
height: 35px !important;
}
.trail-form-message td.form-item * {
min-width: auto !important;
min-height: auto !important;
height: auto !important;
width: auto !important;
float: left;
}
.trail-form-content {
color: #333333;
}
.trail-form-content > .html-display-text {
padding-top: 0px !important;
}
.trail-form-footer {
color: #909090;
font-size: 11px;
line-height: 26px;
}
.trail-form-footer label {
font-size: 11px !important;
}
/*********************************************************** Navigation Tab And Container **********************************************************/
.view-container {
margin-top: 0px;
}
.navigation-tabs.ng-pristine.ng-valid > img{
height: 32px;
}
.navigation-tabs{
top: 8px;
left : -1px;
margin-left: 0px;
}
.btn-group > .btn:last-child,
.btn-group > .dropdown-toggle {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
.btn-group > .btn.large:first-child ,
.btn-group > .btn:first-child{
margin-left: 0;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
.record-toolbar .view-switcher {
margin-left: 10px;
}
.record-toolbar .dropdown a.dropdown-toggle {
margin-left: 3px;
margin-right: -15px;
}
.record-toolbar .menu-bar {
margin-left: 10px;
height: 37px;
padding-bottom: 3px;
}
/* .record-toolbar .button-item{ */
/* line-height: 19px !important; */
/* } */
input.search-query{
margin-bottom: 5px !important;
margin-top: 4px;
padding-bottom: 6px;
}
.navbar .nav li.dropdown > .dropdown-toggle .caret,
.navbar .nav li.dropdown > .dropdown-toggle:hover .caret,
.navbar .nav li.dropdown > .dropdown-toggle:focus .caret{
border-top-color: white;
border-bottom-color: white;
}
.pull-right > .btn{
margin-bottom: 4px !important;
padding: 5px 12px 5px 15px;
border-radius: 6px;
}
table.form-layout td.form-label label.required:after {
color: red;
content: " *";
font-weight: bold;
font-size: 18px;
vertical-align: middle !important;
}
table.form-layout td.form-label label.required{
color: #333333;
}
.form-item .form-item-container.ng-invalid > textarea,
.form-item .form-item-container.ng-invalid > .picker-input input,
.form-item .form-item-container.ng-invalid > .ui-spinner input,
.form-item .form-item-container.ng-invalid > input,
.form-item .form-item-container.ng-invalid > .tag-select,
.form-item .input-append.ng-invalid > input,
.form-item .picker-input.ng-invalid > input{
border-color: red;
}
span[required] > input:focus,span[required] > div > input:focus,span[required]>span>input:focus{
border-color:grey ;
box-shadow: 0px 0px 5px red !important;
}
.view-content{
top : 32px;
}
.view-container .form-view > div:last-child{
top: 44px;
}
.nav-tabs-menu, .nav-tabs-scroll-l,
.nav-tabs-scroll-r, .nav-tabs {
border-bottom: 0;
}
.grid-view > .slickgrid{
top : 42px;
}
.nav-list > li > a, .nav-list .nav-header{
text-shadow: none;
}
/*************************** Grid Icon changes... *********************************************************/
.slick-cell > a{
color:inherit;
font-size: 16px;
}
.slick-cell > a:hover, .slick-cell > a:focus{
color:white;
text-decoration: none;
}
.slick-cell.edit-icon i:hover, .slick-cell.edit-icon i:focus{
color:white;
}
.record-toolbar .button-item > i {
font-size: 16px;
line-height: 20px;
padding-right : 5px;
}
.slick-cell .picker-input > .picker-icons > i[class^=icon] {
color: #222222;
}
/************************ Toolbar menu item ******************************************************/
.record-toolbar .menu-bar a.disabled{
opacity: 0.50;
cursor: default;
}
.record-pager.pull-left > span{
padding : 8px;
}
.splitter-panel{
/* top : 2px; */
}
hr {
margin: 18px 0;
border: 0;
margin-top: -1px !important;
}
sidebar ng-scope splitter-panel{
width : 210px !important;
}

690
sophal/css/custom.css Normal file
View File

@ -0,0 +1,690 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.font-italic{
font-style:italic;
}
.font-normal{
font-weight:normal !important;
}
.font-bold{
font-weight:bold !important;
}
.font-10px{
font-size:10px;
}
.font-11px{
font-size:11px;
}
.font-12px{
font-size:12px;
}
.font-13px{
font-size:13px;
}
.font-14px{
font-size:14px;
}
.font-15px{
font-size:15px;
}
.font-16px{
font-size:16px;
}
.font-18px{
font-size:18px;
}
.font-20px{
font-size:20px !important;
}
.font-22px{
font-size:22px;
}
.font-24px{
font-size:24px;
}
.font-verdana{
font-family:Verdana;
}
.font-times{
font-family: Times New Roman;
}
.font-arial{
font-family: Arial;
}
.font-courier{
font-family: Courier;
}
.font-sans-serif{
font-family: sans-sarif;
}
.font-georgia{
font-family: Aeorgia;
}
.font-trebuchet{
font-family: Trebuchet;
}
.font-lucida-console{
font-family: Lucida Console;
}
.font-tahoma{
font-family: Tahoma;
}
.font-impact{
font-family: Impact;
}
.font-black{
color: black !important;
}
.font-blue{
color: blue !important;
}
.font-grey{
color: grey !important;
}
.font-green{
color: green !important;
}
.font-lime{
color: lime !important;
}
.font-maroon{
color: maroon !important;
}
.font-navy{
color: navy !important;
}
.font-olive{
color: olive !important;
}
.font-orange{
color: orange !important;
}
.font-purple{
color: purple !important;
}
.font-red{
color: red !important;
}
.font-silver{
color: silver !important;
}
.font-white{
color: white !important;
}
.font-yellow{
color: yellow !important;
}
.text-overline{
text-decoration:overline;
}
.text-line-through{
text-decoration: line-through;
}
.text-underline{
text-decoration: underline;
}
.text-left{
text-align:left;
}
.text-right{
text-align:right;
}
.text-center{
text-align: center;
}
.text-justify{
text-align:justify;
}
.text-upper{
text-transform:uppercase;
}
.text-lower{
text-transform:lowercase;
}
.text-capital{
text-transform:capitalize;
}
.text-direction-ltr{
direction:ltr;
}
.text-direction-rtl{
direction:rtl;
}
.button-yellow, .button-yellow:hover, .button-yellow:focus{
border : 1px solid #FFCC00;
color : #333333;
background-image: -webkit-linear-gradient(bottom, #FFCC00, #FFE680);
background-image: -moz-linear-gradient(bottom, #FFCC00, #FFE680);
background-image: linear-gradient(bottom, #FFCC00, #FFE680);
background-color: #FFCC00;
}
.button-blue, .button-blue:hover, .button-blue:focus{
border : 1px solid #3366CC;
color :white;
background-image: -webkit-linear-gradient(bottom, #3366CC, #85A3E0);
background-image: -moz-linear-gradient(bottom, #3366CC, #99B2E6);
background-image: linear-gradient(bottom, #3366CC, #99B2E6);
background-color: #3366CC;
}
.button-black, .button-black:focus, .button-black:hover{
border : 1px solid #00000;
color :white;
background-image: -webkit-linear-gradient(bottom, #000000, #808080);
background-image: -moz-linear-gradient(bottom, #000000, #808080);
background-image: linear-gradient(bottom, #000000, #808080);
background-color: #000000;
}
.button-purple, .button-purple:hover, .button-purple:focus{
border : 1px solid #B280B2;
color :white !important;
background-image: -webkit-linear-gradient(bottom, #660066, #B280B2);
background-image: -moz-linear-gradient(bottom, #660066, #B280B2);
background-image: linear-gradient(bottom, #660066, #B280B2);
background-color: #660066;
}
.button-green, .button-green:focus, .button-green:hover{
border : 1px solid #80CC80;
color :white !important;
background-image: -webkit-linear-gradient(bottom, #009900, #80CC80);
background-image: -moz-linear-gradient(bottom, #009900, #80CC80);
background-image: linear-gradient(bottom, #009900, #80CC80);
background-color: #009900;
}
.button-red, .button-red:focus, .button-red:hover{
border : 1px solid #FF8080;
color :white !important;
background-image: -webkit-linear-gradient(bottom, #FF0000, #FF8080);
background-image: -moz-linear-gradient(bottom, #FF0000, #FF8080);
background-image: linear-gradient(bottom, #FF0000, #FF8080);
background-color: #FF0000;
}
.button-brown, .button-brown:focus, .button-brown:hover{
border : 1px solid #B29980;
color :white;
background-image: -webkit-linear-gradient(bottom, #663300, #B29980);
background-image: -moz-linear-gradient(bottom, #663300, #B29980);
background-image: linear-gradient(bottom, #663300, #B29980);
background-color: #663300;
}
.button-grey, .button-grey:focus, .button-grey:hover{
border : 1px solid #B2B2B2;
color : white !important;
background-image: -webkit-linear-gradient(bottom, grey, #B2B2B2);
background-image: -moz-linear-gradient(bottom, grey, #B29980);
background-image: linear-gradient(bottom, grey, #B29980);
background-color: grey;
}
.button-pink, .button-pink:focus, .button-pink:hover{
border : 1px solid #E680B2;
color : white !important;
background-image: -webkit-linear-gradient(bottom, #CC0066, #E680B2);
background-image: -moz-linear-gradient(bottom, #CC0066, #E680B2);
background-image: linear-gradient(bottom, #CC0066, #E680B2);
background-color: #CC0066;
}
.border-solid{
border-style: solid !important;
}
.border-dotted{
border-style: dotted !important;
}
.border-dashed{
border-style: dashed !important;
}
.border-double{
border-style: double !important;
}
.border-inset{
border-style: inset !important;
}
.border-outset{
border-style: outset !important;
}
.border-hidden{
border-style: hidden !important;
}
.border-red{
border-color: red !important;
}
.border-black{
border-color: black !important;
}
.border-blue{
border-color: blue !important;
}
.border-grey{
border-color: grey !important;
}
.border-green{
border-color: green !important;
}
.border-white{
border-color: white !important;
}
.border-yellow{
border-color: yellow !important;
}
.border-navy{
border-color: navy !important;
}
.border-top{
border-top: 1px solid;
}
.border-bottom{
border-bottom: 1px solid;
}
.border-left{
border-left: 1px solid;
}
.border-right{
border-right: 1px solid;
}
.group-bg-green{
background-color: green !important;
}
.group-bg-gray{
background-color: gray !important;
}
.group-bg-blue{
background-color: blue !important;
}
.group-bg-white{
background-color: white !important;
}
.group-bg-red{
background-color: red !important;
}
.group-bg-yellow{
background-color: yellow !important;
}
.group-bg-olive{
background-color: olive !important;
}
.group-title-green span.ng-binding{
color: green !important;
}
.group-title-gray span.ng-binding{
color: gray !important;
}
.group-title-blue span.ng-binding{
color: blue !important;
}
.group-title-white span.ng-binding{
color: white !important;
}
.group-title-red span.ng-binding{
color: red !important;
}
.group-title-yellow span.ng-binding{
color: yellow !important;
}
.group-title-olive span.ng-binding{
color: olive !important;
}
.group-border-1px{
border : 1px solid;
}
.group-border-2px{
border: 2px solid;
}
.group-border-3px{
border: 3px solid;
}
.group-border-4px{
border: 4px solid;
}
.group-border-5px{
border: 5px solid;
}
.separator-bg-red{
color : white;
background : red;
padding-top: 4px;
padding-left: 5px;
border-bottom: 2px solid red;
}
.separator-bg-yellow{
color : white;
background : yellow;
padding-top: 4px;
padding-left: 5px;
border-bottom: 2px solid yellow;
}
}
.separator-bg-green{
color : white;
background : green;
padding-top: 4px;
padding-left: 5px;
border-bottom: 2px solid green;
}
.separator-bg-blue{
color : white;
background : blue;
padding-top: 4px;
padding-left: 5px;
border-bottom: 1px solid blue;
}
.separator-bg-white{
color : #333333 !important;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #333333;
}
.separator-bg{
color : white;
background : #80B2B2;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #80B2B2;
}
.separator-bg-theme{
color : white;
background : #006666;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #006666;
}
.separator-bg-grey{
color : white;
background : grey;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid grey;
}
.separator-bg-purple{
color : white;
background : purple;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid purple;
}
.separator-bg-black{
color : white;
background : #333333;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #333333;
}
.separator-bg-pink{
color : white;
background : #CC0066;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #CC0066;
}
.separator-bg-brown{
color : white;
background : #663300;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #663300;
}
.rounded-corner{
border-radius : 5px !important;
}
.separator-1px{
border-bottom: 1px solid !important;
}
.separator-2px{
border-bottom: 2px solid !important;
}
.separator-3px{
border-bottom: 3px solid !important;
}
.separator-4px{
border-bottom: 4px solid !important;
}
.separator-5px{
border-bottom: 5px solid !important;
}
.separator-1px-dotted{
border-bottom: 1px dotted !important;
}
.separator-2px-dotted{
border-bottom: 2px dotted !important;
}
.separator-3px-dotted{
border-bottom: 3px dotted !important;
}
.separator-4px-dotted{
border-bottom: 4px dotted !important;
}
.separator-5px-dotted{
border-bottom: 5px dotted !important;
}
.separator-1px-dashed{
border-bottom: 1px dashed !important;
}
.separator-2px-dashed{
border-bottom: 2px dashed !important;
}
.separator-3px-dashed{
border-bottom: 3px dashed !important;
}
.separator-4px-dashed{
border-bottom: 4px dashed !important;
}
.separator-5px-dashed{
border-bottom: 5px dashed !important;
}
.content-bold input {
font-weight:bold;
}
.content-italic input {
font-style:italic;
}
.content-10px input {
font-size:10px;
}
.content-11px input {
font-size:11px;
}
.content-12px input {
font-size:12px;
}
.content-13px input {
font-size:13px;
}
.content-14px input {
font-size:14px;
}
.content-15px input {
font-size:15px;
}
.content-16px input {
font-size:16px;
}
.content-18px input {
font-size:18px;
}
.content-20px input {
font-size:20px;
}
.content-22px input {
font-size:22px;
}
.content-24px input {
font-size:24px;
}
.content-black input{
color: black !important;
}
.content-blue input{
color: blue !important;
}
.content-grey input{
color: grey !important;
}
.content-green input{
color: green !important;
}
.content-lime input{
color: lime !important;
}
.content-maroon input{
color: maroon !important;
}
.content-navy input{
color: navy !important;
}
.content-olive input{
color: olive !important;
}
.content-orange input{
color: orange !important;
}
.content-purple input{
color: purple !important;
}
.content-red input{
color: red !important;
}
.content-silver input{
color: silver !important;
}
.content-white input{
color: white !important;
}
.content-yellow input{
color: yellow !important;
}
.btn-custom {
background-color: transparent;
color: #0275D8;
border: none;
height: 19px !important;
line-height: 13px;
}
.btn-custom.disabled {
background-color: transparent !important;
color: #67ACE7 !important;
cursor: not-allowed !important;
}
.btn-custom .fa {
margin: 0 4px;
}
.btn-custom:hover span {
text-decoration: underline;
}
.btn-custom.disabled:hover span {
text-decoration: none;
}
.btn-custom:hover, .btn-custom:focus, .btn-custom:active {
background-color: transparent !important;
color: #0275D8;
}
.btn-custom:active {
-webkit-box-shadow: unset;
box-shadow: unset;
}

204
sophal/css/login.css Normal file
View File

@ -0,0 +1,204 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
body {
height: 100%;
display: block;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
display: block;
background-color: #2f4050;
padding-top: 60px;
}
@media (max-width: 979px) {
body {
padding-top: 20px;
}
}
header .navbar .brand-logo {
float: left;
}
header .navbar .brand-logo img {
height: 32px;
margin-top: 4px;
}
.panel {
border: 1px solid #d4d4d4;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
background-color: #fff;
}
.panel .panel-header {
background-color: #dadada;
padding: 10px;
text-align: center;
}
.panel .panel-title {
font-size: 18px;
font-weight: 600;
}
.panel .panel-body {
padding: 14px;
overflow: hidden;
}
.panel {
margin-bottom: 18px;
}
.login-panel {
float: inherit !important;
margin: auto !important;
width: 420px;
border: 0;
box-shadow: none;
background-color: transparent;
}
.alert {
width: 420px;
}
@media (min-width: 1200px) {
.alert,
.login-panel {
width: 420px;
}
}
@media (max-width: 767px) {
.alert,
.login-panel {
width: 100%;
}
}
.login-panel .panel-header {
padding: 22px;
}
.login-panel .panel-body {
padding: 0;
}
.login-panel .form-fields,
.login-panel .form-footer {
padding: 22px;
}
.login-panel .form-fields {
margin-top: 8px;
background-color: #eaeaea;
}
.login-panel .form-footer {
background-color: #fff;
}
.login-panel .form-footer button {
width: 100%;
padding: 8px 16px;
font-weight: 600;
}
#login-form {
margin: 0;
}
#login-form input[type="text"],
#login-form input[type="password"],
#login-form .add-on {
border-radius: 0 !important;
padding: 6px 10px;
font-size: 14px;
line-height: 20px;
height: 20px;
}
#login-form .input-prepend {
width: 100%;
margin-bottom: 16px;
}
#login-form .input-prepend input {
width: calc(100% - 60px);
}
#login-form .input-prepend select {
height: 34px;
width: calc(100% - 38px);
}
body > footer {
position: relative;
margin-top: 28px;
height: 28px;
text-align: center;
}
body > footer > p {
margin: 0;
padding: 6px 10px;
}
body > footer > p,
body > footer > p a,
body > footer > p a:hover,
body > footer > p a:active,
body > footer > p a:visited {
color: #f2f2f2;
}
.alert {
box-sizing: border-box;
margin: auto;
margin-top: 8px;
border-radius: 0;
}
.alert h4 {
margin-bottom: 8px;
}
#social-buttons {
display: flex;
flex-direction: column;
padding: 18px 22px;
}
#social-buttons button {
display: flex;
flex: 1;
padding: 8px 10px;
font-weight: 600;
margin: 4px 0;
}
#social-buttons .social-logo {
height: 18px;
width: 18px;
}
#social-buttons .social-title {
padding-left: 8px;
}

974
sophal/css/main.css Normal file
View File

@ -0,0 +1,974 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
html, body {
overflow: hidden;
padding: 0 !important;
margin: 0 !important;
}
body {
height: 100%;
width: 100%;
display: block;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
b, strong {
font-weight: 600;
}
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
}
#container {
left: 0px; right: 0px; top: 41px; bottom: 0px;
position: absolute;
min-width: 320px;
}
@media (max-width: 979px) {
header > .navbar-fixed-top {
position: fixed;
margin-bottom: 0;
}
header > .navbar-fixed-top .navbar-inner {
padding: 0;
}
}
header .navbar .brand-logo {
float: left;
}
header .navbar .brand-logo img {
height: 32px;
margin-top: 4px;
}
header .container-fluid {
padding: 0 4px;
}
@media (max-width: 767px) {
header .container-fluid {
padding: 0 4px;
}
header .navbar-fixed-top {
margin-left: 0;
margin-right: 0;
min-width: 320px;
}
}
header .nav-menu-bar li.empty > a > i {
font-size: 20px;
}
header .nav-menu-bar li.empty > a,
header .navbar .nav-shortcuts > li > a {
padding: 9px 5px 10px;
}
header .nav-menu-bar li > a > img {
width: 16px;
height: 16px;
max-width: 16px;
max-height: 16px;
}
header .navbar .nav-shortcuts > .divider-vertical {
margin: 0 4px;
}
header .navbar .nav-shortcuts > li > a > i {
font-size: 20px;
}
header .navbar .nav-shortcuts > li > a > span {
vertical-align: top;
}
header .navbar .nav-shortcuts .nav-link-mail sup {
position: absolute;
top: 8px;
}
header .navbar .nav-shortcuts .nav-link-tasks .badge {
display: inline-block;
position: absolute;
padding: 5px 4px;
width: 11px;
height: 9px;
top: 1px;
right: -3px;
}
header .navbar .nav-link-user > img {
width: 20px;
height: 20px;
margin-bottom: 1px;
background-color: white;
}
header .navbar .nav-link-user-name,
header .navbar .nav-link-user-sub {
display: block;
font-weight: 600;
opacity: .9;
}
header .navbar .nav-link-user-sub {
font-size: 10px;
opacity: .7;
}
.fill-parent {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.footer {
position: absolute;
left: 0px; right: 0px; bottom: 0px;
padding: 4px 4px 0 4px;
background-color: #eee;
}
.footer p {
margin-bottom: 4px;
text-shadow: 0 1px 1px #f0f0f0;
}
.loading-counter {
padding: 4px 8px 6px 8px;
border-radius: 0px 0px 4px 4px;
}
.blocker-overlay {
opacity: 0;
}
.blocker-overlay.wait {
opacity: 1;
background: rgba(0, 0, 0, 0.5);
transition: opacity ease 0.4s;
}
.blocker-overlay .blocker-wait {
display: none;
position: absolute;
top: 50%;
top: calc(50% - 30px);
width: 100%;
text-align: center;
font-size: 18px;
color: white;
}
.blocker-overlay .blocker-spinner {
margin: auto;
}
.blocker-overlay .blocker-spinner i {
font-size: 42px;
-webkit-animation: fa-spin 1.05s infinite linear;
-moz-animation: fa-spin 1.05s infinite linear;
-o-animation: fa-spin 1.05s infinite linear;
animation: fa-spin 1.05s infinite linear;
}
.blocker-overlay.wait .blocker-wait {
display: block;
}
.spacer {
height: 1em;
}
/* jQuery UI fixes */
.ui-state-hover * {
color: inherit !important;
}
.ui-widget {
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: inherit;
}
.ui-widget-content {
border: 1px solid #d4d4d4;
}
.ui-dialog,
.ui-datepicker,
.ui-menu {
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
}
.tooltip,
.ui-menu,
.ui-datepicker,
.dropdown-menu {
z-index: 9999999999 !important;
}
.tooltip {
font-size: 12px;
}
.tooltip.fade.in {
opacity: 1;
}
.ui-menu {
border: 1px solid rgba(0, 0, 0, 0.2);
}
.ui-menu .ui-menu-item a.ui-state-focus {
color: white;
text-decoration: none;
border-color: #0081C2;
background-color: #0081C2;
background-image: none;
}
.ui-menu .ui-menu-item a:empty {
height: 18px;
}
.ui-datepicker,
.ui-datepicker * {
line-height: 1.1em;
}
.ui-datepicker,
.ui-datepicker table {
font-size: 13px;
}
.ui-datepicker th {
font-weight: 600;
}
.ui-datepicker-header {
background: none;
border: none;
font-weight: 600;
}
.ui-datepicker .ui-state-default {
background: none !important;
border: none !important;
padding: 4px;
}
.ui-datepicker .ui-state-hover {
background: #eee !important;
border: none !important;
}
.ui-datepicker .ui-slider-handle {
padding: 0;
border: 1px solid #ddd !important;
background-image: none !important;
background: #eee !important;
}
.ui-datepicker .ui-slider-handle.ui-state-hover,
.ui-datepicker .ui-state-active {
color: white !important;
border-color: #ccc !important;
background-color: #0074CC !important;
background-image: none !important;
}
.ui-datepicker .ui-state-highlight {
border: 1px solid #08c !important;
padding: 3px;
}
.ui-datepicker .ui-datepicker-next-hover,
.ui-datepicker .ui-datepicker-prev-hover {
top: 2px;
}
.ui-datepicker .ui-datepicker-next-hover {
right: 2px;
}
.ui-datepicker .ui-datepicker-prev-hover {
left: 2px;
}
.ui-autocomplete {
max-height: 340px;
overflow-y: auto;
overflow-x: hidden;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
font-family: inherit;
font-size: 13px;
}
/* end jQuery UI fixes */
#offcanvas {
height: 100%;
}
#offcanvas-toggle {
margin: 0;
margin-left: 4px;
}
#offcanvas-toggle a {
padding-left: 2px;
padding-right: 2px;
}
#sidebar {
width: inherit;
min-width: 249px;
max-width: 249px;
float: left;
height: 100%;
position: relative;
overflow-x: hidden;
overflow-y: auto;
border-right: 1px solid #d3d3d3;
}
#offcanvas.hidden-menu {
position: relative;
left: -250px;
width: calc(100% + 250px);
}
#offcanvas.hidden-menu #sidebar,
#offcanvas-toggle.hidden-menu {
display: none !important;
}
#view-tabs {
height: 100%;
position: absolute;
left: 250px;
right: 0px;
}
@media screen and (min-width: 769px) {
#offcanvas:not(.hidden-menu) {
left: 0;
width: 100%;
position: relative;
transition: all 0.25s ease-out;
}
#offcanvas.inactive {
position: relative;
left: -250px;
width: calc(100% + 250px);
}
#offcanvas.inactive #sidebar {
position: absolute;
top: 0;
}
#sidebar {
overflow: auto !important;
}
}
@media screen and (max-width: 768px) {
#offcanvas {
position: relative;
transition: all 0.25s ease-out;
left: -250px;
width: calc(100% + 250px);
}
#sidebar {
position: absolute;
top: 0;
}
#offcanvas.active {
left: 0;
}
#offcanvas.active #sidebar {
position: relative;
}
}
.navigation-tabs ul.nav-tabs-main {
margin-bottom: 0px;
height: 31px;
}
.navigation-tabs {
position: absolute;
top: 8px; left: 0; right: 0; bottom: 0;
}
.navigation-tabs > .nav-tabs-wrap li.dirty a:before,
.navigation-tabs > .nav-tabs li.dirty a:before {
content: '*';
margin-right: 2px;
}
.navigation-tabs > .nav-tabs-wrap li.notclosable a {
padding-right: 12px;
}
.visible-false {
visibility: hidden;
}
.visible-true {
visibility: inherit;
}
.view-content {
position: absolute;
top: 39px; left: 0px; right: 0px; bottom: 0px;
}
.view-pane {
position: absolute;
top: 0px; left: 0px; right: 0px; bottom: 0px;
padding: 2px;
}
.view-pane > .view-container {
overflow: auto;
}
.view-pane > .view-container,
.view-pane > .view-container > div:not(.slick-editor-dropdown) {
position: absolute;
top: 0px; left: 0px; right: 0px; bottom: 0px;
}
.ui-dialog > [ui-view-popup],
.ui-dialog > [ui-view-popup] > .view-pane,
.ui-dialog > [ui-view-popup] > .view-pane > .view-container,
.ui-dialog > [ui-view-popup] > .view-pane > .view-container .form-view {
overflow: inherit;
display: flex;
flex-flow: column nowrap;
padding: 0 !important;
flex: 1 1 auto;
}
.ui-dialog > [ui-view-popup] > .view-pane,
.ui-dialog > [ui-view-popup] > .view-pane > .view-container,
.ui-dialog > [ui-view-popup] > .view-pane > .view-container > div:not(.slick-editor-dropdown),
.ui-dialog > [ui-view-popup] > .view-pane > .view-container .form-view > div:last-child {
position: inherit;
top: 0 !important;
}
.ui-dialog > [ui-view-popup] > .view-pane > .view-container .grid-view,
.ui-dialog > [ui-view-popup] > .view-pane > .view-container .html-view {
height: 450px;
}
.ui-dialog.maximized > [ui-view-popup] > .view-pane > .view-container .grid-view {
position: absolute !important;
height: auto;
}
.view-pane > .view-container.has-toolbar {
top: -6px;
}
.view-tabs-single .view-pane > .view-container.has-toolbar {
top: 0;
}
.grid-view > .slickgrid {
position: absolute !important;
top: 40px; left: 0px; right: 0px; bottom: 0px;
border: none;
}
.grid-view > .help-item {
margin: 8px;
}
.grid-view.has-details-view > .slickgrid {
right: 50%;
border-right: 1px solid #ddd;
}
.grid-view > .details-view {
position: absolute !important;
top: 40px; left: 50%; right: 0px; bottom: 0px;
}
.grid-view > .details-view > [ui-view-form] {
overflow: auto;
position: absolute;
top: 42px; left: 0; right: 0; bottom: 0;
}
.record-toolbar {
margin: 0;
}
.record-toolbar .navbar-inner {
border-left: none;
border-right: none;
border-radius: 0;
margin: 0;
padding: 0 6px;
border-top: 0;
background-color: #fff;
}
.record-toolbar button {
padding: 4px 12px;
}
.record-toolbar button span {
position: relative;
top: -1px;
}
.record-toolbar button[ui-top-help] {
background: none;
outline: none;
}
.record-toolbar .view-customize,
.record-toolbar .view-switcher {
margin-left: 4px !important;
}
.record-toolbar .view-title {
display: inline-block;
font-weight: 600;
padding-top: 10px;
padding-left: 8px;
}
.record-toolbar .dropdown a.dropdown-toggle {
padding: 4px 8px 4px;
margin-top: 6px;
color: #333;
}
.record-toolbar .nav .dropdown.open a.dropdown-toggle {
color: #222;
}
.record-toolbar .view-toolbar-mobile,
.record-toolbar .view-menubar-mobile {
margin: 0;
}
.record-toolbar-right {
top: 0;
right: 6px;
position: absolute;
}
.grid-view > .details-view .record-toolbar {
}
.grid-view > .details-view .record-toolbar .navbar-inner {
border-top: 1px solid #e7e7e7;
}
@media (max-width: 767px) {
.menu-bar.visible-phone {
display: inline-block !important;
}
.view-toolbar-mobile > li > .dropdown-menu,
.view-menubar-mobile > li > .dropdown-menu {
left: auto;
right: 0;
}
.view-toolbar-mobile > li > .dropdown-menu:before,
.view-menubar-mobile > li > .dropdown-menu:before {
left: auto !important;
right: 12px;
}
.view-toolbar-mobile > li > .dropdown-menu:after,
.view-menubar-mobile > li > .dropdown-menu:after {
left: auto !important;
right: 13px;
}
}
.record-pager-text {
display: inline-block;
margin-top: 9px;
margin-left: 8px;
margin-right: 8px;
font-size: 13px;
}
.record-pager > button,
.record-pager > .btn {
margin-left: 4px;
margin-right: 4px;
margin-top: 6px;
}
.record-pager.pull-right > span,
.record-pager.pull-left > .btn-group {
float: left;
}
.record-pager.pull-left > span {
float: right;
}
.record-pager-change {
display: inline-flex;
}
.record-pager-change {
margin-right: 4px;
}
.record-pager-change button {
height: auto !important;
}
.stackbar {
}
.stackbar .navbar,
.stackbar .navbar * {
color: #333;
}
.stackbar .navbar .navbar-inner {
min-height: 26px;
padding-left: 10px;
padding-right: 10px;
}
.stackbar .navbar .navbar-inner .brand {
font-size: small;
font-weight: 600;
padding: 4px 8px;
margin: 0;
margin-left: -10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - 100px);
}
.stackbar .navbar .navbar-inner .container-fluid {
min-width: 100px;
padding: 0;
}
.stackbar .navbar {
margin-bottom: 0px;
}
.stackbar .navbar,
.stackbar .navbar .navbar-inner {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
box-shadow: none;
}
.html-view,
.iframe-container,
.iframe-container > iframe {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.html-view > .iframe-container {
position: absolute;
top: 0; bottom: 0; left: 0; right: 0;
}
.html-view > .dashlet-body {
overflow: inherit !important;
height: auto !important;
min-height: 50px;
}
.iframe-container {
display: flex;
}
.iframe-container > iframe {
width: 100%;
height: 100%;
flex: 1;
}
.search-view form {
margin: 0 !important;
max-width: 100% !important;
padding: 0 !important;
}
.search-view form .panel {
margin-bottom: 0 !important;
}
.search-view .search-view-form,
.search-view .search-view-toolbar {
margin: 4px;
}
.search-view .search-view-toolbar table.form-layout {
width: inherit;
}
.search-view .search-view-grid {
position: relative;
overflow: hidden;
}
.search-view .search-view-grid .slickgrid {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
border: none;
}
.unselectable-text {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
img.prefix-icon {
margin-right: 4px;
}
.nav-tabs > li > a > img.prefix-icon {
margin-top: -1px;
}
.nav-tabs > li > a > i.prefix-icon {
margin-right: 2px;
}
.fa, [class^=fa-] {
font-size: 14px;
}
.user-preferences > .form-view > .form-view {
max-width: 1200px;
margin: auto;
}
.user-preferences form, form {
padding: 0;
}
.user-preferences-header h3 {
margin: 0;
}
.user-preferences-header {
margin-bottom: 14px;
border-bottom: 1px solid #e7e7e7;
}
.btn-toolbar .nav {
font-size: 13px !important;
margin-bottom: 0;
}
.record-toolbar .menu-bar {
display: inline-block;
float: inherit;
vertical-align: middle;
white-space: nowrap;
}
.device-small .record-toolbar .menu-bar .has-icon .menu-title {
display: none;
}
.record-toolbar .menu-bar,
.record-toolbar .view-toolbar {
margin-left: 0;
margin-right: 0;
}
.record-toolbar .btn-group,
.record-toolbar .button-menu a.btn,
.record-toolbar .record-pager-change button,
.record-toolbar .record-pager-change input {
margin-top: 6px !important;
}
.record-toolbar .button-menu a.btn {
padding: 4px 8px 4px;
}
.record-toolbar .button-menu a.btn {
border-right-width: 0;
}
.record-toolbar .button-menu a.btn img {
margin-top: -2px;
max-width: 16px;
max-height: 16px
}
.record-toolbar .button-menu:last-child a.btn {
border-right-width: 1px;
}
.record-toolbar .menu-bar a.disabled {
color: #ccc;
cursor: default;
}
.record-toolbar .menu-bar a.disabled:hover,
.record-toolbar .menu-bar a.disabled:focus {
background-color: inherit;
background-image: inherit;
}
.record-toolbar .delete-button {
margin: 0 !important;
}
.record-toolbar .delete-button .dropdown-toggle {
padding: 4px;
}
.about-page,
.user-preferences {
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
overflow: auto;
}
.about-page .page-header {
padding-bottom: 4px;
margin: 18px 0 14px;
}
.about-page .page-header h3 {
font-size: 24px;
font-weight: 600;
line-height: 1;
}
.about-page > .content {
padding: 0 18px;
padding-top: 8px;
max-width: 980px;
min-height: calc(100% - 50px);
height: auto;
padding-bottom: 42px;
}
.about-page footer {
font-size: 13px;
clear: both;
position: relative;
padding: 0;
margin-top: -26px;
}
.about-page footer p {
margin: 0;
padding: 4px;
text-align: center;
}
.about-page hr {
margin: 14px 0;
}
.system-page dt {
text-align: left;
}
/* single tab view mode */
.view-tabs-single,
.view-tabs-single .navigation-tabs .nav-tabs {
margin: 0;
}
.view-tabs-single .view-content {
top: 23px;
}
.view-tabs-single .navigation-tabs > .nav-tabs {
position: relative;
top: -9px;
}
.view-tabs-single .navigation-tabs > .nav-tabs > li {
width: 100%;
color: #555;
}
.view-tabs-single .navigation-tabs > .nav-tabs > li > a {
font-size: 14px;
font-weight: 600;
padding: 6px 8px;
border-left: 0;
border-right: 0;
}
.view-tabs-single .navigation-tabs > .nav-tabs > li > i {
width: 28px;
float: right;
font-size: 14px;
line-height: 32px;
text-align: center;
cursor: pointer;
}
.view-tabs-single .navigation-tabs > .nav-tabs > li > span.view-switch-menu > a {
cursor: pointer;
color: #555;
display: block;
line-height: 32px;
}
.view-tabs-single .navigation-tabs > .nav-tabs > li > span.view-switch-menu a[disabled] {
cursor: default;
color: #ccc;
}
.view-tabs-single .navigation-tabs > .nav-tabs > li > span.view-switch-menu a[disabled]:hover {
background-color: #fff;
}
.view-tabs-single .navigation-tabs > .nav-tabs > li > span.view-switch-menu > a > i {
width: 28px;
font-size: 14px;
text-align: center;
margin-top: 11px;
}

File diff suppressed because it is too large Load Diff

770
sophal/css/modern/theme.css Normal file
View File

@ -0,0 +1,770 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
select,
textarea,
input[type="text"],
input[type="password"],
input[type="datetime"],
input[type="datetime-local"],
input[type="date"],
input[type="month"],
input[type="time"],
input[type="week"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"],
.tag-select.picker-input,
.form-item-container > .display-text,
.uneditable-input {
color: #000;
border-color: #eee;
}
.bar-side select,
.bar-side textarea,
.bar-side input[type="text"],
.bar-side input[type="password"],
.bar-side input[type="datetime"],
.bar-side input[type="datetime-local"],
.bar-side input[type="date"],
.bar-side input[type="month"],
.bar-side input[type="time"],
.bar-side input[type="week"],
.bar-side input[type="number"],
.bar-side input[type="email"],
.bar-side input[type="url"],
.bar-side input[type="search"],
.bar-side input[type="tel"],
.bar-side input[type="color"],
.bar-side .tag-select.picker-input,
.bar-side .form-item-container > .display-text,
.bar-side .uneditable-input {
background-color: inherit;
}
.bar-side .ui-spinner {
background: none;
}
table.form-layout .tabbable-tabs .tab-content {
border: 2px solid #E7E7E7;
}
.picker-input > .picker-icons > i.fa {
color: #9F9F9F;
}
.picker-input > .picker-icons > i.fa:hover {
color: #000;
}
[ui-nav-tree] .nav-search input[type="text"] {
background-color: #fff;
}
label,
.label-item span,
.slickgrid .slick-header,
.slickgrid .slick-header-column {
color: #5C5C5C;
font-weight: 400 !important;
}
.panel .panel-title {
color: #3A3A3A;
}
.icons-bar .fa,
.panel-icons .fa,
.links .fa {
color: #8C8C8C;
}
.panel .panel-header {
position: relative;
z-index: 1;
padding: 8px 4px !important;
outline: none !important;
}
.panel {
display: flex;
flex-direction: column;
}
.panel .row-fluid > .form-item {
margin-bottom: 12px;
}
.panel .row-fluid > .form-item-hidden {
margin-bottom: 0px;
}
.panel .panel-header.clickable-header::before {
content: ' ';
display: block;
visibility: hidden;
position: absolute;
top: 1px;
left: 0;
z-index: -2;
background: #f0f0f0;
height: 100%;
width: 100%;
}
.panel .panel-header:hover::before {
visibility: visible;
}
.panel .panel-header.clickable-header::after {
content: ' ';
display: block;
opacity: 0;
position: absolute;
top: 10px;
left: 10px;
z-index: -1;
background: #ddd;
height: 30px;
width: 30px;
border-radius: 100%;
transform-origin: 50% 50%;
}
.panel .panel-header:focus-within:not(:active)::after {
animation: ripple ease-out 1s;
}
[ui-view-form] .panel .panel-header {
padding: 13px 0px 8px 0px;
border-top: 0;
margin-bottom: 8px;
}
[ui-view-form] .panel.collapsed .panel-header {
border-bottom: 1px solid #eee;
}
[ui-view-form] .panel .panel-related .panel-header,
[ui-view-form] .panel-tabs .panel-related .panel-header {
margin-bottom: 0;
}
[ui-view-form] .panel .panel-title {
font-size: 16px;
}
[ui-panel-viewer] > div > h5 {
margin: 4px 0px;
}
.slickgrid .slick-header:hover,
.slickgrid .slick-header-column:hover,
.slickgrid .slick-header.ui-state-hover,
.slickgrid .slick-header-column.ui-state-hover {
color: #000 !important;
}
.slick-row.selected, .slick-row .slick-cell.selected {
background-color: #d3d3d3;
}
.mail-composer textarea {
background: transparent;
}
.mail-message {
border-radius: 3px !important;
box-shadow: 1px 1px 4px rgba(0,0,0,0.12) !important;
}
.mail-message .mail-message-header .subject,
.mail-message .track-fields li {
color: #8C8C8C;
}
.slick-cell.edit-icon {
padding: 2px;
}
.slick-cell.edit-icon i {
width: 100%;
height: 100%;
text-align: center;
}
.slick-cell .picker-icons i.fa:hover {
color: #000;
}
.track-fields span {
color: #000;
}
.mail-followers .panel-header {
border-bottom: 1px solid #eee;
padding: 0;
padding-bottom: 14px;
}
.bar-wrap {
min-height: 0 !important;
background-color: #fff;
}
.panel .bar-wrap {
background-color: inherit;
}
[ui-view-form], .cards-view, .kanban-view {
background: #fafafa;
}
[ui-view-form] > form:not(.mail-thread-view) > div > .bar-main > .bar-wrap {
padding: 16px;
border: 1px solid #eee;
box-shadow: 1px 2px 8px 0px rgba(0, 0, 0, 0.15);
margin-bottom: 22px;
border-radius: 5px;
}
.filter-menu .filter-inputs .tag-select {
border-color: #eee !important;
}
.filter-menu form > div > .bar-main > .bar-wrap,
.chart-controls form > div > .bar-main > .bar-wrap {
box-shadow: none !important;
border: 0 !important;
border-radius: 0 !important;
margin: 0 !important;
padding: 0 !important;
}
[ui-view-form] .panel {
border-top: 0;
border-left: 0;
border-right: 0;
border-bottom: 0;
background-color: inherit;
margin-bottom: 0;
}
.panel .panel-body {
padding: 0;
}
.panel-tabs .tab-content {
border-left: 0;
border-right: 0;
padding: 14px 0;
border-top: 2px solid #E7E7E7;
}
.panel-tabs .nav-tabs {
border-color: #eee;
}
.nav-tabs-wrap {
border-bottom: 0;
}
.nav-tabs > li {
margin-right: 0;
}
.nav-tabs > li > a {
border-radius: 0;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.nav-tabs > li > a,
.nav-tabs-main > li:not(.active) > a {
border-left: 0;
border-right: 0;
}
.panel-tabs .nav-tabs > li > a,
.panel-tabs .nav-tabs > li.active > a {
border: 0;
margin-bottom: -1px;
}
.nav-tabs-main > li:first-child a {
border-left: 1px solid #eee;
}
.nav-tabs-main > li:last-child a {
border-right: 1px solid #eee;
}
form .nav-tabs > li.active > a,
form .nav-tabs > li.active > a:hover,
form .nav-tabs > li.active > a:focus {
border-top-width: 2px;
padding-top: 5px;
color: #0275d8 !important;
}
.panel-tabs .nav-tabs > li {
margin-bottom: 0;
}
form .panel-tabs {
margin-top: 10px;
}
form .panel-tabs .nav-tabs > li:hover > a {
background: #efefef;
border-bottom: 1px solid #efefef;
cursor: pointer;
}
.panel-tabs .nav-tabs > li.active > a,
.panel-tabs .nav-tabs > li.active > a:hover,
.panel-tabs .nav-tabs > li.active > a:focus {
border: 0;
border-bottom: 2px solid #0275d8 !important;
font-weight: 600;
}
.bar-main .mail-messages,
.bar-main .mail-followers,
.bar-main .mail-messages > .panel-body {
border: 0;
padding: 0;
}
.bar-main .mail-message .mail-message-header {
border-bottom: 0;
padding-bottom: 0;
}
.slickgrid .slick-header-column,
.slickgrid .slick-headerrow-column {
border-right: 0 !important;
}
.slickgrid .slick-header-menubutton {
border-left-color: #eee;
}
.slickgrid .slick-header-column.slick-header-column-active,
.slickgrid .slick-header-column.ui-state-hover {
border-left: 1px solid #eee !important;
border-right: 1px solid #eee !important;
padding: 4px 7px;
}
.slickgrid .slick-headerrow-columns .slick-headerrow-column {
background: #fff;
}
.slickgrid .slick-row {
border-bottom: 0 !important;
}
.tree-view-container .tree-header th {
border-left: 0;
}
.tree-view-container .tree-header,
.view-container .tree-view > .tree-view-container {
border-color: #eee;
}
.panel .panel-related .panel-body .slickgrid,
.panel-tabs .panel-related .panel-body .slickgrid,
.panel .form-item .dashlet .dashlet-body .portlet-grid .slickgrid,
.panel-tabs .form-item .dashlet .dashlet-body .portlet-grid .slickgrid {
border-left: 0;
border-right: 0;
border-bottom: 0;
}
.bar-main .wysiwyg-container {
border: 0;
}
textarea {
border-width: 0;
border-bottom-width: 1px;
}
.slick-external-editor textarea {
border-width: 1px;
background-color: #fff;
}
header .navbar .nav .fa-bars, header .navbar .dropdown-toggle .caret,
header .navbar .nav > li > a:focus,
header .navbar .nav > li > a:hover,
header .navbar .nav > li > a:active,
.navbar .nav li.dropdown.open > .dropdown-toggle,
.navbar .nav li.dropdown.active > .dropdown-toggle,
.navbar .nav li.dropdown.open.active > .dropdown-toggle {
color: #2185D0;
}
header .dropdown-menu > li > a:hover,
header .dropdown-menu > li > a:focus,
header .dropdown-submenu:hover > a,
header .dropdown-submenu:focus > a {
color: #fff;
background: #2185D0;
}
.navbar-inner,
.panel .panel-header,
.slickgrid .slick-header,
.nav-tabs-strip .nav-tabs {
border-color: #eee;
}
.slick-headerrow {
border-bottom-color: #eee !important;
}
.popover {
border-radius: 6px;
}
.popover-title {
border-radius: 5px 5px 0 0;
background-color: #fff;
}
.popover.bottom {
background-color: #fff;
}
.popover.bottom .arrow:after {
border-bottom-color: #fff;
}
.popover.bottom .popover-content {
background-color: #fff;
}
[ui-view-form] input,
[ui-view-form] textarea,
[ui-view-form] select,
[ui-view-form] .tag-select {
border-color: #c0c0c0;
}
[ui-view-form] .form-item-container,
[ui-view-form] .form-item-container > span.display-text,
[ui-view-form] .form-item-container input {
min-height: 21px;
line-height: 21px;
}
[ui-view-form] .form-item-container input {
height: 21px;
}
[ui-view-form] .tag-select li.tag-selector input {
min-height: 18px;
height: 18px;
}
[ui-view-form] .tag-select,
[ui-view-form] .tag-select > ul {
min-height: 20px;
}
[ui-view-form] .tag-select {
margin-top: -3px !important;
}
[ui-view-form] .tag-select .tag-text,
[ui-view-form] .tag-select .tag-link,
[ui-view-form] .tag-select-single .tag-link {
height: 14px;
line-height: 12px;
}
[ui-view-form] .tag-select .label,
[ui-view-form] .tag-select .tag-selector {
height: 17px;
}
[ui-view-form] .tag-select .label {
padding: .2em .4em .3em;
}
[ui-view-form] .form-item-container > span.display-text {
padding: 0;
}
.ng-invalid > input,
.ng-invalid > textarea,
.ng-invalid > .picker-input input,
.ng-invalid > .ui-spinner input,
.ng-invalid > .tag-select,
.ng-invalid > .form-item-container > .wysiwyg-container {
border-color: #B94A48;
}
.btn,
.ui-widget-content,
.order-subtotal-total,
.panel-tabs .tab-content,
.mail-thread,
.mail-thread .mail-message {
border-color: #eee;
}
.dropdown-menu {
border: 1px solid rgba(0, 0, 0, 0.05);
}
.btn {
border-radius: 3px !important;
}
.btn-danger {
border-color: #d43f3a;
}
.btn-group > .btn + .btn {
margin-left: 0;
}
.navbar .btn-group .btn:not(:disabled):hover {
background: #2185D0;
color: #fff;
}
.panel-layout .form-item > .btn-group .btn {
border-radius: 0 !important;
}
.bar-side .panel-layout .form-item > .btn {
min-height: 38px;
padding: 8px 10px;
font-weight: 600;
}
.panel-layout .form-item .btn {
position: relative;
border-radius: 3px !important;
box-shadow: 1px 2px 3px rgba(0,0,0,0.16), 1px 2px 3px rgba(0,0,0,0.23);
}
.panel-layout .form-item > .btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 15px;
height: 15px;
background: rgba(255, 255, 255, .5);
opacity: 0;
border-radius: 100px;
transform: scale(1, 1) linear 0.1s;
transform-origin: 50% 50%;
}
@keyframes ripple {
0% {
transform: scale(0, 0);
opacity: 1;
}
100% {
opacity: 0;
transform: scale(100, 100);
}
}
.panel-layout .form-item > .btn:focus:not(:active)::after {
animation: ripple 1s ease-out;
}
.o2m-list .o2m-list-row .toggle-item button {
border-radius: 0 !important;
box-shadow: none;
height: 20px !important;
padding: 0 1px 2px 1px !important;
border-bottom-color: #c0c0c0;
}
.btn-default {
background-color: #fff;
border: 1px solid #0275d8;
color: #0275d8;
}
.o2m-list-row .toggle-item button {
border: 1px solid #eee;
}
.btn-primary {
background-color: #0275d8;
border-color: #0275d8;
color: #fff;
}
.info-button {
border-style: solid;
box-shadow: none !important;
}
.info-button:not([class*=btn-]) > i, .info-button:not([class*=btn-]) .info-button-value {
color: #2185D0;
}
.btn-default:hover,
.btn-default:focus,
.btn-default:active,
.btn-default.active,
.btn-default.disabled,
.btn-default[disabled] {
border: 1px solid #0275d8;
}
.dms-file-list .dms-folder-tree {
border-right: 1px solid #eee;
}
.view-tabs,
.nav-tabs-strip {
background-color: #fff;
}
.order-subtotal dl {
max-width: 280px;
float: right;
}
.order-subtotal dt,
.order-subtotal dd {
padding: 4px;
}
.order-subtotal dt {
padding-right: 8px;
font-weight: normal;
white-space: normal;
width: 55%;
}
.order-subtotal dd {
margin-left: 0;
text-align: right;
}
.order-subtotal dd:empty {
height: 26px;
}
.order-subtotal-total {
border-top: 1px solid #ccc;
font-size: 18px;
font-weight: 600;
}
@media (max-width: 767px) {
.order-subtotal dt {
float: left;
text-align: right;
width: 48%;
}
}
.order-subtotal-total {
border-color: #eee !important;
}
.fc-unthemed th,
.fc-unthemed td,
.fc-unthemed thead,
.fc-unthemed tbody,
.fc-unthemed .fc-divider,
.fc-unthemed .fc-row,
.fc-unthemed .fc-popover {
border-color: #eee;
}
.kanban-column {
background-color: #fff;
box-shadow: 1px 2px 8px 0px rgba(0, 0, 0, 0.15);
}
.kanban-card,
.cards-view .kanban-card {
background-color: #fdfdfd;
}
.kanban-card {
border-left-width: 1px;
box-shadow: 1px 1px 4px rgba(0,0,0,0.12);
}
.kanban-card:hover{
background: #fafafa;
box-shadow: 2px 2px 6px 0 rgba(0,0,0,0.15);
}
.kanban-column .input-group .input-group-btn > .btn {
border-radius: 0 !important;
}
.search-view .bar-wrap {
box-shadow: none !important;
border: 0 !important;
margin: 0 !important;
}
.nav-select a.nav-label,
.nav-select a.nav-label:after {
background-color: #eee;
}
.nav-select a.nav-label:hover,
.nav-select a.nav-label:hover:after {
background-color: #e2e2e2;
}
.dashboard .dashlet-header,
.dashboard .dashlet .dashlet-header {
border-top-width: 1px;
border-top-color: #eee !important;
}
.dashboard .dashlet {
border: 1px solid #e7e7e7;
}
.html > .view-pane,
.dashboard > .view-pane {
top: -7px;
}
.wysiwyg-container,
.wysiwyg-toolbar-top {
border-color: #eee;
}
.wysiwyg-container.ui-resizable .ui-resizable-handle.ui-resizable-s {
background-color: #eee;
}
.progress {
border-radius: 5px !important;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.08);
}

250
sophal/css/navtree.css Normal file
View File

@ -0,0 +1,250 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.nav-tree,
.nav-tree * {
box-sizing: border-box;
}
[ui-nav-tree] .nav-search {
margin: 0;
padding: 8px;
padding-bottom: 0;
}
[ui-nav-tree] .nav-search input {
margin: 0;
box-sizing: border-box;
width: 100%;
height: 28px;
}
[ui-nav-tree] .nav-search-toggle {
text-align: center;
color: #fff;
line-height: 0;
}
[ui-nav-tree] .nav-search-toggle i {
cursor: pointer;
width: 32px;
color: #f3f3f3;
font-weight: 600;
margin-top: -1px;
}
[ui-nav-tree] .nav-search-toggle i:hover {
color: #fff;
}
.sidebar .ui-autocomplete {
border: 0;
box-sizing: border-box;
min-width: 232px !important;
max-width: 232px !important;
max-height: inherit !important;
background: #2f4050;
bottom: 10px;
box-shadow: none;
}
.sidebar .ui-menu-category {
font-weight: 600;
padding: 2px 0;
color: #f3f3f3;
}
.sidebar .ui-menu-item a {
color: #a7b1c2;
}
.sidebar .ui-menu .ui-menu-item a.ui-state-focus {
color: #fff;
border-color: #293846;
background-color: #293846;
}
.nav-tree ul,
.nav-tree li {
list-style: none;
}
.nav-tree li > ul {
display: none;
margin: 0;
}
.nav-tree li.open > ul {
display: block;
}
.nav-tree li.animate > ul {
display: block;
height: 0;
position: relative;
overflow: hidden;
transition: all .35s ease;
}
.nav-tree li > a {
color: #a7b1c2;
text-decoration: none;
outline: none;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 1);
font-weight: 600;
}
.nav-tree li > a,
.nav-tree li > a:focus {
background: transparent;
}
.nav-tree li > a > .nav-title {
display: inline-block;
max-width: 160px;
}
.nav-tree li > a > .nav-image,
.nav-tree li > a > .nav-icon {
margin-right: 6px;
width: 28px;
height: 28px;
float: left;
display: inline-block;
margin-top: -4px;
border-radius: 4px;
}
.nav-tree li > a > .nav-icon {
padding: 4px 7px;
text-shadow: -1px 1px 5px rgba(0, 0, 0, 0.15);
text-align: center;
}
.nav-tree ul li > a > .nav-image,
.nav-tree ul li > a > .nav-icon {
width: 16px;
height: 16px;
margin-top: 2px;
margin-right: 4px;
margin-left: -20px;
}
.nav-tree ul li > a > .nav-icon {
padding: 0;
margin-top: 1px;
}
.nav-tree li > a * {
position: relative;
}
.nav-tree li > a:hover,
.nav-tree li.active > a {
color: #fff;
background-color: #293846;
}
.nav-tree > li {
overflow: hidden;
border-left: 0;
}
.nav-tree > li.open,
.nav-tree > li.active {
background-color: #293846;
}
.nav-tree > li.open {
border-left: 4px solid #0275d8;
transition: all .35s ease;
}
.nav-tree li.folder > a::after {
font-family: "FontAwesome";
font-size: 10px;
content: "\f053";
position: absolute;
right: 16px;
z-index: 2;
}
.nav-tree li.folder.animate > a::after {
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
transform: rotate(-90deg);
transition: all .35s ease;
}
.nav-tree li.folder.open.animate > a::after {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
transition: all .35s ease;
}
.nav-tree li.folder.open > a::after {
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.nav-tree li.tagged > a::after {
content: none;
}
.nav-tree span.nav-tag {
float: right;
text-transform: uppercase;
text-shadow: none;
line-height: 1;
font-size: 10px;
padding: 3px 8px;
border-radius: .25em;
margin-left: 3px;
}
.nav-tree > li > ul > li:last-child {
margin-bottom: 10px;
}
.nav-tree li > a {
padding: 7px 8px;
}
/* 1st level */
.nav-tree > li > a {
padding: 11px 8px;
}
/* 2nd level */
.nav-tree > li > ul > li > a {
padding-left: 42px;
}
/* 3rd level */
.nav-tree > li > ul > li > ul > li > a {
padding-left: 52px;
}
/* 4th level and beyond */
.nav-tree > li > ul > li > ul > li > ul li > a {
padding-left: 62px;
}

418
sophal/css/print.css Normal file
View File

@ -0,0 +1,418 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
html, body {
overflow: inherit;
}
#container,
#container > .fill-parent,
#offcanvas,
#view-tabs,
#view-tabs > .navigation-tabs,
.view-content,
.view-pane,
.view-pane > .view-container,
.view-pane > .view-container .form-view > div:last-child,
.view-pane > .view-container > div:not(.slick-editor-dropdown) {
position: inherit !important;
}
abbr[title]:after,
a[href]:after, .ir a:after,
a[href^="javascript:"]:after,
a[href^="#"]:after {
content: "";
}
.popover, .dropdown-menu,
.ui-menu, .filter-menu {
display: none !important;
}
.ui-dialog {
position: relative !important;
top: 48px !important;
left: inherit !important;
margin: auto !important;
background-color: white !important;
}
header .navbar-inner {
border-color: #d4d4d4 !important;
}
#container {
top: 16px;
}
.view-content,
.view-container .portal-view,
.view-container .form-view > div:last-child {
top: 16px !important;
}
.view-print-header,
.view-print-header h3 {
text-align: center;
font-size: 17px !important;
}
.splitter-container .sidebar.splitter-panel,
.splitter-container .splitter-vertical {
display: none;
}
.splitter-container .splitter-panel {
width: 0;
}
.splitter-container .splitter-panel.view-tabs {
width: 100% !important;
left: 0 !important;
}
.nav-menu-bar {
display: none !important;
}
.nav.nav-shortcuts {
display: none;
}
.navigation-tabs .axelor-logo {
top: -16px !important;
border: none;
}
.navigation-tabs > .nav-tabs-wrap,
.navigation-tabs .record-toolbar {
display: none;
}
.nav-tabs {
border: none;
display: none !important;
}
.bar-wrap {
border: none !important;
box-shadow: none !important;
padding: 0 !important;
margin-bottom: 0 !important;
}
.form-item .tabbable-tab,
.form-item .tab-content {
border: none !important;
margin: 0 !important;
padding: 0 !important;
}
.form-item .nav-tabs-wrap {
display: none;
}
.panel,
.panel-header,
.panel-body {
border: none !important;
margin: 0 !important;
padding: 6px 0 !important;
}
.panel-body {
min-height: auto !important;
}
.panel, .panel-tabs, .panel-stack, .bar-layout .dashboard {
margin-bottom: 0 !important;
}
.panel-tabs .tab-content > .panel {
padding: 0 !important;
}
.panel-tabs .tab-content > .panel > .panel-body {
padding: 0 !important;
}
.panel-header .icons-bar {
display: none !important;
}
.dashlet-buttons {
display: none;
}
.report-box .report-tags {
right: 0;
}
.dashlet.chart,
.chart-container {
page-break-inside: avoid;
}
.nvtooltip {
display: none !important;
}
.dashboard .dashlet {
border: none !important;
}
.dashboard .dashlet-header {
border-top: none;
padding: 10px 0 !important;
}
.dashlet.grid .dashlet-body {
height: auto !important;
overflow: inherit !important;
position: relative !important;
}
.form-item-group > legend > i,
.form-item-group > legend > i:before {
display: none !important;
}
.form-item-group > legend > span {
background: initial !important;
background-color: white !important;
}
.btn {
display: none;
}
.picker-input > .picker-icons {
display: none !important;
}
input,
select,
textarea,
.tag-select {
border: none !important;
box-shadow: none !important;
transition: inherit;
}
.dms-folder-tree {
display: none !important;
}
.dms-file-list > .slickgrid {
left: 0 !important;
border: none !important;
}
.grid-view,
form[ui-form] {
overflow: hidden;
}
.cards-view, .kanban-view {
top: 0;
position: relative !important;
overflow: inherit !important;
}
.kanban-column {
border: none !important;
margin-bottom: 0 !important;
}
.kanban-column .input-group {
display: none !important;
}
.kanban-column ul.kanban-card-list {
padding: 0 !important;
}
.dashlet-body .slickgrid,
.dashlet-body .slickgrid .slick-viewport,
.panel-body .slickgrid,
.panel-body .slickgrid .slick-viewport,
.grid-view > .slickgrid,
.grid-view > .slickgrid .slick-viewport {
overflow: inherit !important;
position: relative !important;
}
.dashlet-body .slickgrid .slick-viewport,
.panel-body .slickgrid .slick-viewport,
.grid-view > .slickgrid .slick-viewport {
height: auto !important;
}
.grid-view > .slickgrid {
top: 0;
}
.slickgrid,
.slickgrid .slick-header-column {
border: 0 !important;
}
.slick-row, .slick-cell {
border-bottom-style: none !important;
}
.slick-headerrow {
display: none !important;
}
.stackbar > .navbar .icons-bar {
display: none !important;
}
.slick-row .slick-cell.edit-icon,
.slick-row .slick-cell.move-icon {
display: none;
}
.portlet .divider-vertical {
width: 0 !important;
border: none !important;
}
.nav-select .nav-step:not(.active) {
display: none !important;
}
.nav-select .nav-step.active a {
padding: 0 !important;
}
.nav-select .nav-step.active a:after {
display: none !important;
}
.mail-followers {
margin-left: 16px !important;
}
.mail-followers .fa {
display: none !important;
}
.mail-messages {
margin: 0 !important;
padding: 0 !important;
}
.mail-composer {
display: none;
}
.mail-thread {
margin-top: 0 !important;
}
.mail-thread .avatar {
background-color: #ddd !important;
}
.mail-thread .mail-message > .arrow {
background-color: #fff !important;
}
.mail-message-icons {
display: none !important;
}
.mail-thread-more {
display: none !important;
}
[ui-view-form] > form:not(.mail-thread-view) .mail-messages,
[ui-view-form] > form:not(.mail-thread-view) .mail-followers {
display: none !important;
}
.ibox .box:before,
.iswitch .box:before {
margin: 0 !important;
padding: 0 !important;
box-shadow: none;
border: none !important;
}
.ibox .box:after,
.iswitch .box:after {
margin: 0 !important;
padding: 0 !important;
box-shadow: none;
}
.ibox .box:before,
.iswitch .box:after {
content: "\f00d" !important;
font-family: FontAwesome;
transform: none;
}
.ibox input:checked + .box:before,
.ibox input:checked + .box:before {
border: none !important;
background-color: #fff !important;
}
.iswitch input:checked + .box:after {
transform: none;
width: auto;
top: 0;
left: 0;
}
.iswitch input:checked + .box:after {
content: "\f00c";
font-family: FontAwesome;
}
.ibox input:checked + .box:after {
display: none;
}
/** responsive */
header {
display: none;
}
#sidebar {
display: none;
}
#view-tabs {
left: 0;
}
#container,
.view-content,
.view-container .form-view > div:last-child,
.navigation-tabs {
top: 0 !important;
}
.panel-header .menu-bar {
display: none;
}

466
sophal/css/slickgrid.css Normal file
View File

@ -0,0 +1,466 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.slickgrid {
background: white;
outline: 0;
border: 1px solid #DDD;
border-top: 0 !important;
}
.slickgrid,
.slickgrid * {
box-sizing: border-box;
}
.slickgrid .slick-header,
.slickgrid .slick-header-column {
color: #333;
background: #fff;
background-image: none;
}
.slickgrid .slick-header {
border: 0;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
.slickgrid .slick-header-column {
border-right: 1px solid #ddd;
}
.slickgrid .slick-header-column:last-child {
border-right-color: transparent;
}
.slickgrid-empty .slick-header,
.slickgrid-empty .slick-headerrow {
height: 0;
border-bottom: none;
}
.slickgrid .slick-cell,
.slickgrid .slick-header-column {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
-ms-box-sizing: content-box;
box-sizing: content-box;
}
.slick-row {
background: #f9f9f9;
border-bottom: 1px solid #e7eaec !important;
}
.slick-row.odd {
background: #fff;
}
.slick-row:hover {
background: #f5f5f5;
}
.slick-row .slick-cell {
border: 0;
padding: 2px 8px;
}
.slick-row .slick-cell {
box-sizing: border-box;
line-height: 20px;
height: 100% !important;
}
.slick-row.selected,
.slick-row .slick-cell.selected {
background-color: #abb9d3;
}
.slick-row .slick-cell.text,
.slick-row .slick-cell.html {
white-space: pre-wrap;
word-wrap: break-word;
}
.slick-row .slick-cell.selected.active::after {
content: " ";
display: block;
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
border: 1px dotted #76849c;
z-index: -1;
}
.slickgrid-editable .slick-row.active .slick-cell-editable:before {
content: " ";
display: block;
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
border: 1px solid #8080ff;
background-color: #fff;
z-index: -1;
}
.slickgrid-editable .slick-row.active .slick-cell-required:before {
background-color: #d2d2ff;
}
.slick-headerrow {
height: 24px;
border: 0 !important;
border-bottom: 1px solid #ddd !important;
background: #fff !important;
background-image: none !important;
}
.slick-headerrow-columns .slick-headerrow-column {
background: #f9f9f9;
}
.slick-headerrow-column {
height: 24px;
border: none !important;
border-right: 1px solid #ddd !important;
background: #f5f5f5;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.slick-headerrow-column:last-child {
border-right: transparent !important;
}
.slick-headerrow input {
margin: 0;
padding: 2px 8px;
width: 100%;
height: 100%;
outline: 0;
border: none;
border-radius: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
box-shadow: none !important;
}
.slick-headerrow i.combo-icon {
position: absolute;
font-size: 14px;
top: 5px;
right: 6px;
}
.slick-header-column.ui-state-default {
height: inherit;
line-height: inherit;
padding: 4px 8px;
font-weight: 600;
}
.slick-headerrow-column.ui-state-default {
padding: 0px;
}
.slick-cell.editable {
background: white;
padding: 0;
}
.slick-cell input,
.slick-cell .picker-input,
.slick-cell .ui-spinner,
.slick-cell [x-field] {
outline: 0;
margin: 0;
padding: 0;
border-radius: 0;
box-sizing: border-box;
width: 100%;
height: 100% !important;
min-width: inherit !important;
min-height: 23px;
}
.slick-cell .picker-input {
width: 100% !important;
}
.slick-cell .ui-spinner {
border: none;
}
.slick-cell input {
padding-left: 6px !important;
}
.slick-cell textarea {
border-radius: 0;
margin: 0;
outline: 0;
}
.slick-cell .text-item {
height: auto !important;
}
.slick-cell .picker-input > .picker-icons > i.fa {
padding-top: 2px !important;
}
.slick-cell input[type=checkbox],
.slick-cell .form-item-container input[type=checkbox] {
width: auto;
height: auto;
margin-top: 0 !important;
}
.slick-cell .form-item-container > .ibox {
width: auto;
margin-top: 2px !important;
padding: 0;
float: inherit !important;
}
.slick-editor-dropdown {
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
min-width: 250px;
}
.ui-dialog [ui-dialog] > .slickgrid {
position: absolute;
top: -1px; right: 0; bottom: -4px; left: 0;
border: 0;
}
.ui-dialog .slick-editor-dropdown .slickgrid {
position: relative;
top: 0; right: 0; border: 0; left: 0;
}
.slickgrid-editable .slick-row.active .slick-cell-editable.dirty:before,
.slick-cell.dirty {
background-image: url('../img/dirty.gif');
background-position: 0 0;
background-repeat: no-repeat;
}
.slick-cell .input-append > button {
display: none;
}
.slick-cell.editable * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.slick-cell.button img {
cursor: pointer;
}
.slick-cell.button .disabled {
outline: none;
opacity: 0.45;
cursor: inherit;
}
.slick-cell.button,
.slick-cell-checkboxsel {
text-align: center;
}
.slick-cell-checkboxsel input {
width: inherit;
}
.slick-header-column.slick-cell-checkboxsel {
padding-top: 2px;
}
.slick-group > .slick-cell-checkboxsel {
text-align: inherit;
}
.slick-header-column.integer,
.slick-header-column.decimal,
.slick-cell.integer,
.slick-cell.decimal {
text-align: right;
}
.slick-header-column.boolean,
.slick-cell.boolean {
text-align: center;
}
.slick-cell.boolean .fa {
box-sizing: content-box;
border: 1px solid #ccc;
padding: 2px;
width: 14px;
height: 14px;
}
.slick-headerrow input::-webkit-input-placeholder {
font-size: 11px;
}
.slick-headerrow input::-moz-placeholder {
font-size: 11px;
}
.slick-headerrow input::-ms-input-placeholder {
font-size: 11px;
}
.slick-group-toggle {
width: 9px;
height: 9px;
margin-right: 5px;
}
.slick-group-toggle.collapsed {
background: url(../lib/slickgrid/images/expand.gif) no-repeat center center;
}
.slick-group-toggle.expanded {
background: url(../lib/slickgrid/images/collapse.gif) no-repeat center center;
}
.slick-group-text,
.slick-group-count {
font-weight: 600;
}
.slick-group-count {
color: green;
}
.slick-group {
}
.slick-group-totals {
font-weight: 600;
}
.slick-group-totals,
.slick-group-totals .slick-cell {
background-color: #e9e9e9;
}
.slick-sort-indicator {
margin-left: 7px;
}
.slick-sort-indicator-asc,
.slick-sort-indicator-desc {
display: inline-block;
}
.slick-header-menu {
z-index: 1150;
padding: 0;
min-width: 100px;
border: 1px solid rgba(0, 0, 0, 0.2);
background: #fff;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
}
.slick-header-menuitem {
padding: 2px 4px;
padding-right: 20px;
border: 1px solid transparent;
}
.slick-header-menu > div:first-of-type {
margin-top: 4px;
}
.slick-header-menu > div:last-of-type {
margin-bottom: 4px;
}
.slick-header-menuitem:hover {
color: #fff;
background-color: #0081c2;
}
.slick-header-menusep {
margin: 4px 1px;
background-color: #e5e5e5;
border-bottom: 1px solid #ffffff;
}
.slick-header-menubutton {
width: 16px;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
}
.slick-cell:not(.button) > a {
color: #0088cc;
}
.slick-cell.edit-icon i,
.slick-cell i.slick-icon {
cursor: pointer;
}
.slick-cell.move-icon {
cursor: move;
}
.panel-related.readonly .slick-cell.move-icon {
cursor: default;
}
.panel-related.readonly .slick-cell.move-icon::before {
content: "";
}
.slickgrid-empty-text {
display: none;
}
.slickgrid-empty-message .slickgrid-empty-text {
display: block !important;
position: absolute;
top: 48%;
width: 100%;
font-weight: 400;
text-align: center;
}
.slickgrid-empty-message .grid-canvas {
box-sizing: border-box;
}
.slickgrid-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: #ddd;
opacity: 0.25;
z-index: 2;
}

98
sophal/css/tabs.css Normal file
View File

@ -0,0 +1,98 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.nav-tabs-wrap {
position: relative;
}
.nav-tabs-wrap a {
outline: none;
}
.nav-tabs-menu,
.nav-tabs-scroll-l,
.nav-tabs-scroll-r {
position: absolute;
width: 16px;
top: 0px;
bottom: 0px;
margin-top: .8em;
border-bottom: 1px solid #DDD;
display: none;
}
.nav-tabs-scroll-r {
right: 0px;
}
.nav-tabs-scroll-l a,
.nav-tabs-scroll-r a {
margin: auto;
display: block;
width: 6px;
text-decoration: none;
}
.nav-tabs-menu {
right: 0;
}
.nav-tabs-scroll-l.disabled a,
.nav-tabs-scroll-r.disabled a {
color: #999999;
cursor: default;
}
.nav-tabs-strip {
position: relative;
overflow: hidden;
height: 36px;
}
.nav-tabs-strip .nav-tabs {
display: block;
position: absolute !important;
width: 100000px;
}
.nav-tabs-closable li {
position: relative;
}
.nav-tabs-scrollable li > a,
.nav-tabs-closable li > a {
border: 1px solid #EEE;
border-bottom: 1px solid #DDD;
}
.nav-tabs-closable li > a {
padding-right: 18px;
}
.nav-tabs-closable li > i {
position: absolute;
top: 5px;
right: 6px;
color: #ccc;
cursor: pointer;
font-size: 11px;
}
.nav-tabs-closable .active > i,
.nav-tabs-closable li > i:hover {
color: #555;
}

View File

@ -0,0 +1,677 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@import url('../custom.css');
.font-italic{
font-style:italic;
}
.font-normal{
font-weight:normal !important;
}
.font-bold{
font-weight:bold !important;
}
.font-10px{
font-size:10px;
}
.font-11px{
font-size:11px;
}
.font-12px{
font-size:12px;
}
.font-13px{
font-size:13px;
}
.font-14px{
font-size:14px;
}
.font-15px{
font-size:15px;
}
.font-16px{
font-size:16px;
}
.font-18px{
font-size:18px;
}
.font-20px{
font-size:20px !important;
}
.font-22px{
font-size:22px;
}
.font-24px{
font-size:24px;
}
.font-verdana{
font-family:Verdana;
}
.font-times{
font-family: Times New Roman;
}
.font-arial{
font-family: Arial;
}
.font-courier{
font-family: Courier;
}
.font-sans-serif{
font-family: sans-sarif;
}
.font-georgia{
font-family: Aeorgia;
}
.font-trebuchet{
font-family: Trebuchet;
}
.font-lucida-console{
font-family: Lucida Console;
}
.font-tahoma{
font-family: Tahoma;
}
.font-impact{
font-family: Impact;
}
.font-black{
color: black !important;
}
.font-blue{
color: blue !important;
}
.font-grey{
color: grey !important;
}
.font-green{
color: green !important;
}
.font-lime{
color: lime !important;
}
.font-maroon{
color: maroon !important;
}
.font-navy{
color: navy !important;
}
.font-olive{
color: olive !important;
}
.font-orange{
color: orange !important;
}
.font-purple{
color: purple !important;
}
.font-red{
color: red !important;
}
.font-silver{
color: silver !important;
}
.font-white{
color: white !important;
}
.font-yellow{
color: yellow !important;
}
.text-overline{
text-decoration:overline;
}
.text-line-through{
text-decoration: line-through;
}
.text-underline{
text-decoration: underline;
}
.text-left{
text-align:left;
}
.text-right{
text-align:right;
}
.text-center{
text-align: center;
}
.text-justify{
text-align:justify;
}
.text-upper{
text-transform:uppercase;
}
.text-lower{
text-transform:lowercase;
}
.text-capital{
text-transform:capitalize;
}
.text-direction-ltr{
direction:ltr;
}
.text-direction-rtl{
direction:rtl;
}
.button-yellow, .button-yellow:hover, .button-yellow:focus{
border : 1px solid #FFCC00;
color : #333333;
background-image: -webkit-linear-gradient(bottom, #FFCC00, #FFE680);
background-image: -moz-linear-gradient(bottom, #FFCC00, #FFE680);
background-image: linear-gradient(bottom, #FFCC00, #FFE680);
background-color: #FFCC00;
}
.button-blue, .button-blue:hover, .button-blue:focus{
border : 1px solid #3366CC;
color :white;
background-image: -webkit-linear-gradient(bottom, #3366CC, #85A3E0);
background-image: -moz-linear-gradient(bottom, #3366CC, #99B2E6);
background-image: linear-gradient(bottom, #3366CC, #99B2E6);
background-color: #3366CC;
}
.button-black, .button-black:focus, .button-black:hover{
border : 1px solid #000000;
color :white;
background-image: -webkit-linear-gradient(bottom, #000000, #808080);
background-image: -moz-linear-gradient(bottom, #000000, #808080);
background-image: linear-gradient(bottom, #000000, #808080);
background-color: #000000;
}
.button-purple, .button-purple:hover, .button-purple:focus{
border : 1px solid #B280B2;
color :white !important;
background-image: -webkit-linear-gradient(bottom, #660066, #B280B2);
background-image: -moz-linear-gradient(bottom, #660066, #B280B2);
background-image: linear-gradient(bottom, #660066, #B280B2);
background-color: #660066;
}
.button-green, .button-green:focus, .button-green:hover{
border : 1px solid #80CC80;
color :white !important;
background-image: -webkit-linear-gradient(bottom, #009900, #80CC80);
background-image: -moz-linear-gradient(bottom, #009900, #80CC80);
background-image: linear-gradient(bottom, #009900, #80CC80);
background-color: #009900;
}
.button-red, .button-red:focus, .button-red:hover{
border : 1px solid #FF8080;
color :white !important;
background-image: -webkit-linear-gradient(bottom, #FF0000, #FF8080);
background-image: -moz-linear-gradient(bottom, #FF0000, #FF8080);
background-image: linear-gradient(bottom, #FF0000, #FF8080);
background-color: #FF0000;
}
.button-brown, .button-brown:focus, .button-brown:hover{
border : 1px solid #B29980;
color :white;
background-image: -webkit-linear-gradient(bottom, #663300, #B29980);
background-image: -moz-linear-gradient(bottom, #663300, #B29980);
background-image: linear-gradient(bottom, #663300, #B29980);
background-color: #663300;
}
.button-grey, .button-grey:focus, .button-grey:hover{
border : 1px solid #B2B2B2;
color : white !important;
background-image: -webkit-linear-gradient(bottom, grey, #B2B2B2);
background-image: -moz-linear-gradient(bottom, grey, #B29980);
background-image: linear-gradient(bottom, grey, #B29980);
background-color: grey;
}
.button-pink, .button-pink:focus, .button-pink:hover{
border : 1px solid #E680B2;
color : white !important;
background-image: -webkit-linear-gradient(bottom, #CC0066, #E680B2);
background-image: -moz-linear-gradient(bottom, #CC0066, #E680B2);
background-image: linear-gradient(bottom, #CC0066, #E680B2);
background-color: #CC0066;
}
.border-solid{
border-style: solid !important;
}
.border-dotted{
border-style: dotted !important;
}
.border-dashed{
border-style: dashed !important;
}
.border-double{
border-style: double !important;
}
.border-inset{
border-style: inset !important;
}
.border-outset{
border-style: outset !important;
}
.border-hidden{
border-style: hidden !important;
}
.border-red{
border-color: red !important;
}
.border-black{
border-color: black !important;
}
.border-blue{
border-color: blue !important;
}
.border-grey{
border-color: grey !important;
}
.border-green{
border-color: green !important;
}
.border-white{
border-color: white !important;
}
.border-yellow{
border-color: yellow !important;
}
.border-navy{
border-color: navy !important;
}
.border-top{
border-top: 1px solid;
}
.border-bottom{
border-bottom: 1px solid;
}
.border-left{
border-left: 1px solid;
}
.border-right{
border-right: 1px solid;
}
.group-bg-green{
background-color: green !important;
}
.group-bg-gray{
background-color: gray !important;
}
.group-bg-blue{
background-color: blue !important;
}
.group-bg-white{
background-color: white !important;
}
.group-bg-red{
background-color: red !important;
}
.group-bg-yellow{
background-color: yellow !important;
}
.group-bg-olive{
background-color: olive !important;
}
.group-title-green span.ng-binding{
color: green !important;
}
.group-title-gray span.ng-binding{
color: gray !important;
}
.group-title-blue span.ng-binding{
color: blue !important;
}
.group-title-white span.ng-binding{
color: white !important;
}
.group-title-red span.ng-binding{
color: red !important;
}
.group-title-yellow span.ng-binding{
color: yellow !important;
}
.group-title-olive span.ng-binding{
color: olive !important;
}
.group-border-1px{
border : 1px solid;
}
.group-border-2px{
border: 2px solid;
}
.group-border-3px{
border: 3px solid;
}
.group-border-4px{
border: 4px solid;
}
.group-border-5px{
border: 5px solid;
}
.separator-bg-red{
color : white;
background : red;
padding-top: 4px;
padding-left: 5px;
border-bottom: 2px solid red;
}
.separator-bg-yellow{
color : white;
background : yellow;
padding-top: 4px;
padding-left: 5px;
border-bottom: 2px solid yellow;
}
.separator-bg-green{
color : white;
background : green;
padding-top: 4px;
padding-left: 5px;
border-bottom: 2px solid green;
}
.separator-bg-blue{
color : white;
background : blue;
padding-top: 4px;
padding-left: 5px;
border-bottom: 1px solid blue;
}
.separator-bg-white{
color : #333333 !important;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #333333;
}
.separator-bg{
color : white;
background : #80B2B2;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #80B2B2;
}
.separator-bg-theme{
color : white;
background : #006666;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #006666;
}
.separator-bg-grey{
color : white;
background : grey;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid grey;
}
.separator-bg-purple{
color : white;
background : purple;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid purple;
}
.separator-bg-black{
color : white;
background : #333333;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #333333;
}
.separator-bg-pink{
color : white;
background : #CC0066;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #CC0066;
}
.separator-bg-brown{
color : white;
background : #663300;
padding-top: 5px;
padding-left: 5px;
border-bottom: 2px solid #663300;
}
.rounded-corner{
border-radius : 5px !important;
}
.separator-1px{
border-bottom: 1px solid !important;
}
.separator-2px{
border-bottom: 2px solid !important;
}
.separator-3px{
border-bottom: 3px solid !important;
}
.separator-4px{
border-bottom: 4px solid !important;
}
.separator-5px{
border-bottom: 5px solid !important;
}
.separator-1px-dotted{
border-bottom: 1px dotted !important;
}
.separator-2px-dotted{
border-bottom: 2px dotted !important;
}
.separator-3px-dotted{
border-bottom: 3px dotted !important;
}
.separator-4px-dotted{
border-bottom: 4px dotted !important;
}
.separator-5px-dotted{
border-bottom: 5px dotted !important;
}
.separator-1px-dashed{
border-bottom: 1px dashed !important;
}
.separator-2px-dashed{
border-bottom: 2px dashed !important;
}
.separator-3px-dashed{
border-bottom: 3px dashed !important;
}
.separator-4px-dashed{
border-bottom: 4px dashed !important;
}
.separator-5px-dashed{
border-bottom: 5px dashed !important;
}
.content-bold input {
font-weight:bold;
}
.content-italic input {
font-style:italic;
}
.content-10px input {
font-size:10px;
}
.content-11px input {
font-size:11px;
}
.content-12px input {
font-size:12px;
}
.content-13px input {
font-size:13px;
}
.content-14px input {
font-size:14px;
}
.content-15px input {
font-size:15px;
}
.content-16px input {
font-size:16px;
}
.content-18px input {
font-size:18px;
}
.content-20px input {
font-size:20px;
}
.content-22px input {
font-size:22px;
}
.content-24px input {
font-size:24px;
}
.content-black input{
color: black !important;
}
.content-blue input{
color: blue !important;
}
.content-grey input{
color: grey !important;
}
.content-green input{
color: green !important;
}
.content-lime input{
color: lime !important;
}
.content-maroon input{
color: maroon !important;
}
.content-navy input{
color: navy !important;
}
.content-olive input{
color: olive !important;
}
.content-orange input{
color: orange !important;
}
.content-purple input{
color: purple !important;
}
.content-red input{
color: red !important;
}
.content-silver input{
color: silver !important;
}
.content-white input{
color: white !important;
}
.content-yellow input{
color: yellow !important;
}
/* */
.button-item.client-img-menu img{
width: 50%;
}
.button-item.client-img-menu img:hover{
width: 80%;
}
.button-item.client-img-menu span{
color: #000;
}
li a[href="#/about"] {
display: none;
visibility: hidden;
}

View File

@ -0,0 +1,87 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.view-container .calendar-view {
display: flex;
flex-direction: column;
}
.view-container .calendar-view > div:last-child {
display: flex;
padding: 4px;
background-color: #fff;
max-height: 100%;
flex: 1;
}
.calendar-main {
flex: 1;
height: 100%;
}
.calendar-side {
overflow: hidden;
margin-left: 4px;
max-width: 228px;
display: flex;
flex-direction: column;
height: 100%;
}
.calendar-side .ui-datepicker {
box-shadow: none;
border-radius: 0;
}
.calendar-legend {
padding-left: 4px;
margin-top: 8px;
margin-bottom: 0;
overflow: auto;
flex: 1;
}
.calendar-legend label {
line-height: 20px;
}
.calendar-view .record-pager > span {
font-weight: 600;
padding: 10px 4px 0;
}
.calendar-view .fc-month-view .calendar-event-day * {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.calendar-view .fc-event .fc-bg {
opacity: .1;
}
.calendar-bubble-content {
overflow: hidden;
}
.calendar-bubble-content hr {
margin: 8px 0;
}
.calendar-view .fc-widget-header {
font-weight: 600;
}

490
sophal/css/view.dms.css Normal file
View File

@ -0,0 +1,490 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.dms-file-list {
position: relative;
overflow: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.dms-file-list .record-toolbar .breadcrumb {
margin: 0;
padding: 11px 8px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
background-color: #fff;
}
.dms-file-list .record-toolbar .breadcrumb a {
color: #0088cc;
}
.dms-file-list .record-toolbar .btn {
outline: none;
}
.dms-file-list > .slickgrid,
.dms-file-list .dms-folder-tree,
.dms-file-list .dms-details {
transition: all 0.25s;
}
.dms-file-list .dms-folder-tree {
position: absolute;
overflow: auto;
width: 250px;
top: 41px;
bottom: 0;
left: 0;
border-right: 1px solid #d3d3d3;
background-color: #fff;
}
.dms-file-list .dms-details {
position: absolute;
right: -1px;
top: 41px;
bottom: 0;
width: 249px;
margin: 0;
border-bottom: 0;
background-color: white;
box-shadow: 0 6px 12px rgba(0,0,0,.175);
z-index: 2;
}
.dms-file-list .dms-details.ng-hide {
right: -261px;
}
.dms-file-list .dms-details > .panel-body {
position: absolute;
top: 48px;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
}
.dms-file-list .dms-details > .panel-header .panel-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - 20px);
box-sizing: border-box;
}
.dms-file-list .dms-details input.not-readonly {
background: inherit !important;
border: 0;
box-shadow: none;
margin-bottom: 5px;
}
.dms-file-list .dms-details label {
cursor: default;
font-weight: 600;
}
.dms-file-list .dms-details .tags > .label {
float: left;
margin-right: 5px;
margin-bottom: 5px;
padding: 4px 6px;
border-radius: 0;
}
.dms-file-list .dms-details .tags form {
margin: 0;
padding: 0;
width: 100%;
}
.dms-file-list .dms-details .tags .panel {
margin: 0;
}
.dms-file-list .dms-details .tags a {
color: #222;
float: left;
margin-right: 5px;
}
.dms-file-list > .slickgrid {
left: 250px;
}
.dms-file-list.has-no-tree .dms-folder-tree {
left: -251px;
}
.dms-file-list.has-no-tree > .slickgrid {
left: 0 !important;
}
.dms-file-list .slick-icon.fa-file-text {
color: #337ab7;
}
.dms-file-list .slick-icon.fa-table {
color: #0275d8;
}
.dms-folder-dialog input {
box-sizing: border-box;
width: 100%;
height: 28px;
margin: 0;
}
.dms-file-upload .upload-list,
.dms-file-upload .upload-dropzone {
position: absolute;
z-index: 10000;
background: white;
}
.dms-file-upload .upload-dropzone {
border: 1px solid #d4d4d4;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
width: 250px;
height: 250px;
border-radius: 125px;
text-align: center;
top: calc(50% - 125px);
left: calc(50% - 125px);
z-index: 10001;
display: none;
}
.dms-file-upload .upload-dropzone .drop-image {
margin-top: 36%;
margin-bottom: 12px;
}
.dms-file-upload .upload-dropzone .drop-image i {
font-size: 32px !important;
}
.dms-file-list .dms-details .panel-header,
.dms-file-upload .panel-header {
overflow: hidden;
}
.dms-file-list .dms-details .panel-header a,
.dms-file-upload .panel-header a {
color: #222;
}
.dms-file-upload .panel-body {
padding: 0;
}
.dms-file-upload .upload-list * {
box-sizing: border-box;
}
.dms-file-upload .upload-list {
bottom: 0;
right: 10px;
width: 480px;
font-size: 12px;
}
.dms-file-upload .upload-list-item {
border-bottom: 1px solid #d4d4d4;
padding: 2px;
position: relative;
overflow: hidden;
}
.dms-file-upload .upload-list .panel-body {
max-height: 420px;
overflow: auto;
}
.dms-file-upload .upload-list-item:last-child {
border-bottom: 0;
}
.dms-file-upload .upload-list-item > div {
padding: 2px;
margin: 0;
min-height: inherit;
float: left;
white-space: nowrap;
text-overflow: ellipsis;
text-align: right;
overflow: hidden;
}
.dms-file-upload .upload-list .file-name {
width: 60%;
}
.dms-file-upload .upload-list .file-progress {
width: 25%;
}
.dms-file-upload .upload-list .file-op {
width: 15%;
}
.dms-file-upload .upload-list .progress {
margin-bottom: 0;
}
.dms-file-upload .upload-list .progress,
.dms-file-upload .upload-list .progress .bar {
-webkit-transition: inherit;
transition: inherit;
}
@media (max-width: 767px) {
.dms-file-upload .upload-list {
left: 8px;
right: 8px;
width: auto;
}
.dms-file-upload .upload-list-item > div {
width: 100% !important;
}
}
.dms-file-upload .upload-overlay {
opacity: 0.3;
with: 100%;
height: 100%;
overflow: hidden;
z-index: 10000;
position: absolute;
top: 0px;
left: 0;
right: 0;
bottom: 0;
background-color: gray;
display: none;
}
.dms-file-list.dropping .upload-overlay,
.dms-file-list.dropping .upload-dropzone {
display: block;
}
.dms-folder-tree {
box-sizing: border-box;
}
.dms-folder-tree * {
box-sizing: border-box;
}
.dms-folder-tree ul {
margin: 0 0 0px 15px;
}
.dms-folder-tree ul,
.dms-folder-tree li {
list-style: none;
white-space: nowrap;
}
.dms-folder-tree li {
text-overflow: ellipsis;
overflow: hidden;
}
.dms-folder-tree > ul {
margin: 1px 4px;
}
.dms-folder-tree li > a {
color: #333;
text-decoration: none;
line-height: 2em;
padding: 4px;
outline: none;
}
.dms-folder-tree li > a * {
position: relative;
}
.dms-folder-tree li > a span.highlight {
position: absolute;
box-sizing: border-box;
width: 100%;
height: 2em;
left: 0;
}
.dms-folder-tree li > a i.handle {
width: 16px;
display: inline-block;
text-align: center;
}
.dms-folder-tree li.empty i.handle {
visibility: hidden;
}
.dms-folder-tree li > a:hover span.highlight {
background-color: rgba(0, 0, 0, .05);
}
.dms-folder-tree li > a.active span.highlight {
background-color: rgba(0, 0, 0, .1);
}
.dms-folder-tree li > a.dropping span.highlight {
border: 1px solid rgba(82, 168, 236, 0.8);
}
.dms-folder-tree li > a.active {
color: #222;
font-weight: 600;
}
.dms-folder-tree li.dms-tree-more > a > span.title {
padding-left: 20px;
font-style: oblique;
}
.grid-dnd-proxy {
position: absolute;
display: inline-block;
padding: 6px 12px;
background: #fff;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
z-index: 100000;
}
.grid-dnd-proxy .badge {
position: absolute;
bottom: calc(100% - 6px);
left: calc(100% - 6px);
}
.dms-popup {
margin-top: -1px;
padding: 1px 0 0 !important;
}
.ui-dialog .ui-dialog-content.dms-popup {
height: 450px !important;
overflow: inherit !important;
}
.ui-dialog.maximized .ui-dialog-content.dms-popup {
height: 100% !important;
max-height: 100% !important;
}
.dms-popup > div,
.dms-popup > div > .grid-view {
height: 100%;
box-sizing: border-box;
}
.dms-popup > div > .grid-view {
position: absolute;
top: 2px;
right: 0;
bottom: -4px;
left: 0;
border: 0;
}
.dms-permission-popup {
}
.dms-permission-popup [x-field="user"] input,
.dms-permission-popup [x-field="group"] input {
border-right: none;
}
.dms-permission-popup .picker-input i.fa-times {
font-size: 11px;
position: relative;
top: -1px;
color: #d4d4d4;
}
.dms-permission-popup .picker-input i.fa-times:hover {
color: #222;
}
.spreadsheet-item {
border: 1px solid #e7e7e7;
}
.spreadsheet-item .ui-resizable-s {
background-color: #e7e7e7;
border-color: #e7e7e7;
}
.dms-folder-tree i.fa-folder {
font-size: 18px;
vertical-align: middle;
margin-right: 4px;
margin-top: -1px;
}
i.fa-colored.fa-folder {
color: #FFA726;
}
i.fa-colored.fa-file-o {
color: #767676;
}
i.fa-colored.fa-file-text-o {
color: #2185D0;
}
i.fa-colored.fa-file-word-o {
color: #2185D0;
}
i.fa-colored.fa-file-excel-o {
color: #21BA45;
}
i.fa-colored.fa-file-powerpoint-o {
color: #a33e03;
}
i.fa-colored.fa-file-pdf-o {
color: #DB2828;
}
i.fa-colored.fa-file-image-o {
color: #6435C9;
}
i.fa-colored.fa-file-video-o {
color: #DB2828;
}
i.fa-colored.fa-file-archive-o {
color: #A5673F;
}

View File

@ -0,0 +1,202 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.ibox {
display: inline-block;
width: inherit;
margin: 0;
overflow: hidden;
}
.form-inline .ibox {
vertical-align: middle;
margin-bottom: 0;
}
.ibox.readonly {
cursor: default;
opacity: 0.6;
}
.ibox .box {
display: inline-block;
position: relative;
width: 20px;
height: 20px;
float: left;
outline: none;
}
.ibox input {
opacity: 0 !important;
margin-left: -14px !important;
position: absolute;
}
.ibox .title {
margin-left: 8px;
}
.ibox .item-label {
color: #999;
}
.ibox .box:before,
.ibox .box:after {
box-sizing: border-box;
}
.ibox.round .box:before {
border-radius: 20px;
}
.ibox .box:before {
content: " ";
display: inline-block;
border: 1px solid #ccc;
height: 100%;
width: 100%;
transition: all .2s;
}
.ibox input:focus + .box:before {
border: 1px solid #0275d8;
}
.ibox:hover .box:before,
.ibox:hover input:focus + .box:before {
border: 2px solid #0275d8;
}
.ibox input:checked + .box:before {
border: 1px solid #0275d8;
background-color: #0275d8;
}
.ibox input:checked + .box:after {
content: " ";
display: inline-block;
position: absolute;
top: 5px;
left: 4px;
width: 12px;
height: 7px;
border: 2px solid #fff;
border-top: 0;
border-right: 0;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.ibox:hover input:disabled + .box:before {
border: 1px solid #ccc;
}
.ibox:hover input:disabled:checked + .box:before {
border: 1px solid #0275d8;
}
.readonly > .ibox,
.readonly > .iswitch {
cursor: inherit;
}
.readonly > .ibox > .box,
.readonly > .iswitch {
opacity: 0.6;
}
.form-item.iswitch {
margin-top: 4px;
}
.iswitch {
position: relative;
display: inline-block;
width: 35px;
height: 20px;
margin: 0;
cursor: pointer;
background-color: #0275d8;
border-radius: 30px
}
.iswitch.readonly {
cursor: default;
opacity: 0.6;
}
.iswitch input {
position: absolute;
opacity: 0;
filter: alpha(opacity=0)
}
.iswitch input:checked + .box:before {
top: 50%;
right: 5px;
bottom: 50%;
left: 50%;
border-width: 0;
border-radius: 5px
}
.iswitch input:checked + .box:after {
margin-left: 16px
}
.iswitch .box:before {
content: "";
position: absolute;
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 30px;
transition: all .2s
}
.iswitch input:focus + .box:before {
border: 1px solid #0275d8;
}
.iswitch .box:after {
content: "";
position: absolute;
top: 1px;
bottom: 1px;
width: 18px;
background-color: #fff;
border-radius: 50%;
box-shadow: 1px 1px 3px rgba(0,0,0,0.25);
transition: margin-left .3s
}
.ng-invalid > .ibox .box:before,
.ng-invalid > .iswitch .box:before {
border-color: #B94A48;
}
.form-item-container > .ibox,
.form-item-container > .iswitch {
margin: 4px 0 !important;
float: left;
}

1562
sophal/css/view.form.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,500 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
[class *= hilite-] > .display-text {
color: inherit;
}
[class *= hilite-].strong,
[class *= hilite-].strong input:first-of-type,
[class *= hilite-].strong textarea:first-of-type {
font-weight: 600 !important;
}
.hilite-primary-text,
.hilite-primary-text input:first-of-type,
.hilite-primary-text textarea:first-of-type {
color: #428bca !important;
}
.hilite-primary,
.hilite-primary input:first-of-type,
.hilite-primary textarea:first-of-type,
.hilite-primary .slick-cell {
color: #fff !important;
background-color: #428bca !important;
}
.hilite-warning-text,
.hilite-warning-text input:first-of-type,
.hilite-warning-text textarea:first-of-type {
color: #f0ad4e !important;
}
.hilite-warning,
.hilite-warning input:first-of-type,
.hilite-warning textarea:first-of-type,
.hilite-warning .slick-cell {
color: #fff !important;
background-color: #f0ad4e !important;
}
.hilite-success-text,
.hilite-success-text input:first-of-type,
.hilite-success-text textarea:first-of-type {
color: #5cb85c !important;
}
.hilite-success-text,
.hilite-success-text input:first-of-type,
.hilite-success-text textarea:first-of-type {
color: #5b0077 !important;
}
.hilite-success,
.hilite-success input:first-of-type,
.hilite-success textarea:first-of-type,
.hilite-success .slick-cell {
color: #fff !important;
background-color: #5cb85c !important;
}
.hilite-danger-text,
.hilite-danger-text input:first-of-type,
.hilite-danger-text textarea:first-of-type {
color: #d9534f !important;
}
.hilite-danger,
.hilite-danger input:first-of-type,
.hilite-danger textarea:first-of-type,
.hilite-danger .slick-cell {
color: #fff !important;
background-color: #d9534f !important;
}
.hilite-error-text,
.hilite-error-text input:first-of-type,
.hilite-error-text textarea:first-of-type {
color: #b94a48 !important;
}
.hilite-error,
.hilite-error input:first-of-type,
.hilite-error textarea:first-of-type,
.hilite-error .slick-cell {
color: #fff !important;
background-color: #b94a48 !important;
}
.hilite-info-text,
.hilite-info-text input:first-of-type,
.hilite-info-text textarea:first-of-type {
color: #5bc0de !important;
}
.hilite-info,
.hilite-info input:first-of-type,
.hilite-info textarea:first-of-type,
.hilite-info .slick-cell {
color: #fff !important;
background-color: #5bc0de !important;
}
.hilite-default-text,
.hilite-default-text input:first-of-type,
.hilite-default-text textarea:first-of-type {
}
.hilite-default,
.hilite-default input:first-of-type,
.hilite-default textarea:first-of-type,
.hilite-default .slick-cell {
}
.slick-row[class*="hilite-"].selected,
.slick-row[class*="hilite-"] .slick-cell.selected {
background-image: linear-gradient(
rgba(0, 0, 0, 0.2),
rgba(0, 0, 0, 0.2)
);
}
.slick-row[class*="-text"].selected,
.slick-row[class*="-text"] .slick-cell.selected {
background-image: none;
}
.slickgrid-editable .slick-row[class*="hilite-"].active .slick-cell-editable:before,
[class*="hilite-"].form-item-container {
background-color: inherit !important;
}
/** other predefined styles */
.form-item label.label-bold,
.form-item-container.bold,
.form-item-container.bold input:first-of-type,
.form-item-container.bold textarea:first-of-type {
font-weight: 600 !important;
}
.form-item label.label-italic,
.form-item-container.italic,
.form-item-container.italic input:first-of-type,
.form-item-container.italic textarea:first-of-type {
font-style: italic !important;
}
.form-item label.label-underline,
.form-item-container.underline,
.form-item-container.underline input:first-of-type,
.form-item-container.underline textarea:first-of-type {
text-decoration: underline !important;
}
.form-item label.label-overline,
.form-item-container.overline,
.form-item-container.overline input:first-of-type,
.form-item-container.overline textarea:first-of-type {
text-decoration: overline !important;
}
.form-item label.label-line-through,
.form-item-container.line-through,
.form-item-container.line-through input:first-of-type,
.form-item-container.line-through textarea:first-of-type {
text-decoration: line-through !important;
}
.form-item label.label-small,
.form-item-container.small,
.form-item-container.small input:first-of-type,
.form-item-container.small textarea:first-of-type {
font-size: 10px !important;
}
.form-item label.label-big,
.form-item-container.big,
.form-item-container.big input:first-of-type,
.form-item-container.big input:first-of-type {
font-size: 16px !important;
}
.form-item label.label-large,
.form-item-container.large,
.form-item-container.large input:first-of-type,
.form-item-container.large textarea:first-of-type {
font-size: 18px !important;
}
.form-item label.label-left,
.form-item-container.left,
.form-item-container.left input:first-of-type,
.form-item-container.left textarea:first-of-type {
text-align: left !important;
}
.form-item label.label-right,
.form-item-container.right,
.form-item-container.right input:first-of-type,
.form-item-container.right textarea:first-of-type {
text-align: right !important;
}
.form-item label.label-center,
.form-item-container.center,
.form-item-container.center input:first-of-type,
.form-item-container.center textarea:first-of-type {
text-align: center !important;
}
.form-item label.label-justify,
.form-item-container.justify,
.form-item-container.justify input:first-of-type,
.form-item-container.justify textarea:first-of-type {
text-align: justify !important;
}
.form-item label.label-red,
.form-item-container.red,
.form-item-container.red input:first-of-type,
.form-item-container.red textarea:first-of-type {
color: red !important;
}
.form-item label.label-blue,
.form-item-container.blue,
.form-item-container.blue input:first-of-type,
.form-item-container.blue textarea:first-of-type {
color: blue !important;
}
.form-item label.label-green,
.form-item-container.green,
.form-item-container.green input:first-of-type,
.form-item-container.green input:first-of-type {
color: green !important;
}
.form-item label.label-muted,
.form-item-container.muted,
.form-item-container.muted input:first-of-type,
.form-item-container.muted textarea:first-of-type {
color: #777 !important;
}
.form-item label.label-primary,
.form-item-container.primary,
.form-item-container.primary input:first-of-type,
.form-item-container.primary textarea:first-of-type {
color: #428bca !important;
}
.form-item label.label-success,
.form-item-container.success,
.form-item-container.success input:first-of-type,
.form-item-container.success textarea:first-of-type {
color: #3c763d !important;
}
.form-item label.label-info,
.form-item-container.info,
.form-item-container.info input:first-of-type,
.form-item-container.info textarea:first-of-type {
color: #31708f !important;
}
.form-item label.label-warning,
.form-item-container.warning,
.form-item-container.warning input:first-of-type,
.form-item-container.warning textarea:first-of-type {
color: #8a6d3b !important;
}
.form-item label.label-danger,
.form-item-container.danger,
.form-item-container.danger input:first-of-type,
.form-item-container.danger textarea:first-of-type {
color: #a94442 !important;
}
.form-item label.label-bg-primary,
.form-item-container.bg-primary,
.form-item-container.bg-primary input:first-of-type,
.form-item-container.bg-primary textarea:first-of-type {
color: #fff;
background-color: #428bca !important;
}
.form-item label.label-bg-success,
.form-item-container.bg-success,
.form-item-container.bg-success input:first-of-type,
.form-item-container.bg-success textarea:first-of-type {
background-color: #dff0d8 !important;
}
.form-item label.label-bg-info,
.form-item-container.bg-info,
.form-item-container.bg-info input:first-of-type,
.form-item-container.bg-info textarea:first-of-type {
background-color: #d9edf7 !important;
}
.form-item label.label-bg-warning,
.form-item-container.bg-warning,
.form-item-container.bg-warning input:first-of-type,
.form-item-container.bg-warning textarea:first-of-type {
background-color: #fcf8e3 !important;
}
.form-item label.label-bg-danger,
.form-item-container.bg-danger,
.form-item-container.bg-danger input:first-of-type,
.form-item-container.bg-danger textarea:first-of-type {
background-color: #f2dede !important;
}
.font-bold {
font-weight: 600;
}
.font-noraml {
font-weight: 400;
}
.bg-red { background-color: #DB2828; }
.bg-orange { background-color: #F2711C; }
.bg-yellow { background-color: #FBBD08; }
.bg-olive { background-color: #B5CC18; }
.bg-green { background-color: #21BA45; }
.bg-teal { background-color: #00B5AD; }
.bg-blue { background-color: #2185D0; }
.bg-violet { background-color: #6435C9; }
.bg-purple { background-color: #A333C8; }
.bg-pink { background-color: #E03997; }
.bg-brown { background-color: #A5673F; }
.bg-grey { background-color: #767676; }
.bg-black { background-color: #1B1C1D; }
.bg-white { background-color: #F9FAFB; }
.fg-red { color: #DB2828; }
.fg-orange { color: #F2711C; }
.fg-yellow { color: #B58105; }
.fg-olive { color: #8ABC1E; }
.fg-green { color: #1EBC30; }
.fg-teal { color: #10A3A3; }
.fg-blue { color: #2185D0; }
.fg-violet { color: #6435C9; }
.fg-purple { color: #A333C8; }
.fg-pink { color: #E03997; }
.fg-brown { color: #A5673F; }
.fg-grey { color: #767676; }
.fg-black { color: #1B1C1D; }
.fg-white { color: #F9FAFB; }
.panel-red .panel .panel-header,
.panel-red .dashlet .dashlet-header {
border-top-color: #DB2828;
}
.panel-red.panel .panel-header {
border-top-color: #DB2828 !important;
}
.panel-red.dashlet .dashlet-header {
border-top-color: #DB2828 !important;
}
.panel-orange .panel .panel-header,
.panel-orange .dashlet .dashlet-header {
border-top-color: #F2711C;
}
.panel-orange.panel .panel-header {
border-top-color: #F2711C !important;
}
.panel-orange.dashlet .dashlet-header {
border-top-color: #F2711C !important;
}
.panel-yellow .panel .panel-header,
.panel-yellow .dashlet .dashlet-header {
border-top-color: #FBBD08;
}
.panel-yellow.panel .panel-header {
border-top-color: #FBBD08 !important;
}
.panel-yellow.dashlet .dashlet-header {
border-top-color: #FBBD08 !important;
}
.panel-olive .panel .panel-header,
.panel-olive .dashlet .dashlet-header {
border-top-color: #B5CC18;
}
.panel-olive.panel .panel-header {
border-top-color: #B5CC18 !important;
}
.panel-olive.dashlet .dashlet-header {
border-top-color: #B5CC18 !important;
}
.panel-green .panel .panel-header,
.panel-green .dashlet .dashlet-header {
border-top-color: #21BA45;
}
.panel-green.panel .panel-header {
border-top-color: #21BA45 !important;
}
.panel-green.dashlet .dashlet-header {
border-top-color: #21BA45 !important;
}
.panel-teal .panel .panel-header,
.panel-teal .dashlet .dashlet-header {
border-top-color: #00B5AD;
}
.panel-teal.panel .panel-header {
border-top-color: #00B5AD !important;
}
.panel-teal.dashlet .dashlet-header {
border-top-color: #00B5AD !important;
}
.panel-blue .panel .panel-header,
.panel-blue .dashlet .dashlet-header {
border-top-color: #2185D0;
}
.panel-blue.panel .panel-header {
border-top-color: #2185D0 !important;
}
.panel-blue.dashlet .dashlet-header {
border-top-color: #2185D0 !important;
}
.panel-violet .panel .panel-header,
.panel-violet .dashlet .dashlet-header {
border-top-color: #6435C9;
}
.panel-violet.panel .panel-header {
border-top-color: #6435C9 !important;
}
.panel-violet.dashlet .dashlet-header {
border-top-color: #6435C9 !important;
}
.panel-purple .panel .panel-header,
.panel-purple .dashlet .dashlet-header {
border-top-color: #A333C8;
}
.panel-purple.panel .panel-header {
border-top-color: #A333C8 !important;
}
.panel-purple.dashlet .dashlet-header {
border-top-color: #A333C8 !important;
}
.panel-pink .panel .panel-header,
.panel-pink .dashlet .dashlet-header {
border-top-color: #E03997;
}
.panel-pink.panel .panel-header {
border-top-color: #E03997 !important;
}
.panel-pink.dashlet .dashlet-header {
border-top-color: #E03997 !important;
}
.panel-brown .panel .panel-header,
.panel-brown .dashlet .dashlet-header {
border-top-color: #A5673F;
}
.panel-brown.panel .panel-header {
border-top-color: #A5673F !important;
}
.panel-brown.dashlet .dashlet-header {
border-top-color: #A5673F !important;
}
.panel-grey .panel .panel-header,
.panel-grey .dashlet .dashlet-header {
border-top-color: #767676;
}
.panel-grey.panel .panel-header {
border-top-color: #767676 !important;
}
.panel-grey.dashlet .dashlet-header {
border-top-color: #767676 !important;
}
.panel-black .panel .panel-header,
.panel-black .dashlet .dashlet-header {
border-top-color: #1B1C1D;
}
.panel-black.panel .panel-header {
border-top-color: #1B1C1D !important;
}
.panel-black.dashlet .dashlet-header {
border-top-color: #1B1C1D !important;
}

View File

@ -0,0 +1,878 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.bar-layout {
max-width: 1500px;
margin: auto;
padding: 14px;
}
.bar-layout.mid-form {
max-width: 768px;
}
.bar-layout.mini-form {
max-width: 480px;
}
.bar-layout.large-form {
max-width: 1500px;
}
.bar-container {
display: flex;
}
.bar-main {
flex: 1;
max-width: 100%;
}
.bar-wrap {
min-height: 0 !important;
}
.bar-side {
width: 25%;
min-width: 15em;
margin-left: 14px;
}
.bar-container.has-side > .bar-main {
max-width: 75%;
}
@media (max-width: 767px) {
.bar-container {
flex-direction: column;
}
.bar-main {
flex: inherit;
max-width: inherit !important;
}
.bar-side {
margin: 0;
width: inherit;
min-width: auto;
}
}
.panel-layout * {
box-sizing: border-box;
}
.panel {
background-color: #fff;
}
.panel .panel-header {
padding: 11px 15px 14px;
border-top: 4px solid #e7e7e7;
border-bottom: 1px solid #e7e7e7;
margin-top: -1px;
display: flex;
align-items: center;
outline: none;
}
.panel-nested > .panel-header {
border-top: 0;
padding: 6px 0;
margin-bottom: 4px;
}
.panel .panel-title {
font-size: 14px;
font-weight: 600;
flex: 1;
}
.panel .panel-header .panel-icon {
margin-right: 8px;
padding: 2px 4px;
border-radius: 4px;
}
.panel .panel-header .panel-icon.has-bg {
text-shadow: -1px 1px 5px rgba(0, 0, 0, 0.15);
}
.panel .panel-header .panel-image {
width: 16px;
height: 16px;
margin-right: 8px;
}
.panel .panel-header .panel-icons {
margin-left: 4px;
}
.panel .panel-header .panel-icons a {
color: #c4c4c4;
}
.panel .panel-header .menu-bar {
margin: 0;
}
.panel .panel-header .menu-bar > .menu > a {
padding: 0 4px;
background-image: none;
border: 0;
}
.panel .panel-header .menu-bar .caret {
margin-top: 8px;
}
.panel .panel-header .dropdown-menu {
left: inherit;
right: 0;
}
.panel.collapsed .panel-header {
border-bottom: 0;
}
.clickable-header{
cursor: pointer;
overflow: hidden;
outline: none !important;
}
.panel .panel-body {
padding: 14px;
transition: height 0.3s ease-out;
}
.panel {
margin-bottom: 16px;
border: 1px solid #e7e7e7;
}
.panel.attached {
margin-top: -16px;
}
.panel.noframe {
border: none;
box-shadow: none;
}
.panel .row-fluid > .form-item {
margin-bottom: 6px;
}
.panel.noframe .panel-body {
padding: 0;
}
.panel .row-fluid > .form-item-hidden {
margin-bottom: 0;
}
.panel .row-fluid > .form-item-hidden:first-child + .form-item {
margin-left: 0;
}
.panel .row-fluid:last-child > .form-item-hidden {
margin-bottom: 1px;
}
.panel[x-stacked] .row-fluid:last-child > .form-item {
margin-bottom: 6px;
}
.panel[x-stacked] .row-fluid:last-child > .form-item-hidden,
.panel[x-stacked] .row-fluid:last-child > .form-item:last-child {
margin-bottom: 1px;
}
.panel[x-stacked] .row-fluid:last-child > .form-item-hidden,
.panel[x-stacked] .row-fluid:last-child > .form-item:last-child,
.panel[x-stacked] .row-fluid:last-child > .form-item:last-child > .panel {
margin-bottom: 1px;
}
@media (max-width: 767px) {
.panel .row-fluid:last-child > .form-item {
margin-bottom: 6px;
}
.panel .row-fluid:last-child > .form-item:last-child {
margin-bottom: 1px;
}
}
.panel-layout .panel:not(.panel-related) {
border: none;
box-shadow: none;
margin-bottom: 0;
}
.panel-layout .bar-layout,
.panel-layout .panel .panel-body {
padding: 0;
}
.panel-layout .o2m-list .panel .row-fluid > .form-item {
margin-bottom: 6px;
}
.panel-layout .form-item-container.readonly > a,
.panel-layout .form-item-container.readonly > input,
.panel-layout .form-item-container.readonly > span.display-text {
padding-left: 0;
}
.panel-layout .readonly > .o2m-list > .o2m-list-row > a {
color: #0088cc;
}
.panel-layout .form-item-container input {
min-width: inherit;
}
.panel-layout .form-item-container.time-item input,
.panel-layout .form-item-container.datetime-item .picker-input {
width: 100%;
}
.panel-layout .form-item-container input[type="radio"],
.panel-layout .form-item-container input[type="checkbox"] {
margin-top: 0 !important;
}
.panel-layout .form-item-container,
.panel-layout .form-item-container > span.display-text {
min-height: 28px;
border-bottom: 1px solid #00000030;
}
.panel-layout .form-item-container.editable input[type="text"],
.panel-layout .form-item-container.editable input[type="password"],
.panel-layout .form-item-container.editable input[type="number"],
.panel-layout .form-item-container.editable input[type="email"],
.panel-layout .form-item-container.editable input[type="url"],
.panel-layout .form-item-container.editable .tag-select {
border-top-width: 0;
border-left-width: 0;
border-right-width: 0;
padding-left: 0;
}
.panel-layout .slick-cell .form-item-container.editable input[type="text"],
.panel-layout .slick-cell .form-item-container.editable input[type="password"],
.panel-layout .slick-cell .form-item-container.editable input[type="number"],
.panel-layout .slick-cell .form-item-container.editable input[type="email"],
.panel-layout .slick-cell .form-item-container.editable input[type="url"],
.panel-layout .slick-cell .form-item-container.editable .tag-select {
border-top-width: 1px;
border-left-width: 1px;
border-right-width: 1px;
padding-left: 6px;
}
.panel-layout .slick-cell .form-item-container.editable .tag-select {
padding-left: 0;
}
.panel-layout .slick-cell .form-item-container,
.panel-layout .slick-cell .form-item-container > span.display-text {
min-height: auto;
}
.panel-layout .slick-cell .form-item-container > span.display-text {
padding: 2px 8px;
}
.panel-layout .image-item .btn,
.panel-layout .file-item .btn {
width: 32px;
padding: 2px 6px 2px;
height: 28px;
}
.panel-layout .tag-select,
.panel-layout .tag-select > ul {
min-height: 27px;
}
.panel-layout .tag-select .tag-text,
.panel-layout .tag-select .tag-link,
.panel-layout .tag-select-single .tag-link {
height: 16px;
line-height: 16px;
font-size: 11px;
}
.panel-layout .label-item,
.panel-layout .label-item span,
.panel-layout label span.title {
font-weight: 800; /*sophal*/
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
margin-bottom: 0;
height: 18px;
}
.panel-layout .label-item span {
display: inline-block;
max-width: 100%;
}
.panel-related .panel-header {
border-bottom: none;
}
.panel-related .panel-header .icons-bar {
padding-top: 0;
}
.panel-related .panel-header .icons-bar i {
margin: 0 4px;
padding: inherit;
}
.panel-related .panel-body {
padding: 0;
position: relative;
min-height: 1px;
}
.panel-related .panel-body .slickgrid {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
border: none;
}
.panel-related.noEdit .edit-icon i:before,
.panel-related.readonly .edit-icon i:before {
content: "\f0f6";
}
.panel-related.ng-invalid {
border-color: #B94A48;
}
.panel-related.ng-invalid .panel-header {
border-top-color: #B94A48;
}
.panel-tabs,
.panel-stack,
.bar-layout .dashboard {
margin-bottom: 16px;
}
.panel-tabs .nav-tabs > li > a {
border-color: #e7e7e7;
}
.panel-tabs .nav-tabs > li.active > a {
color: #333;
border-bottom-color: transparent !important;
}
.panel-tabs .tab-content {
padding: 14px;
overflow: inherit;
background-color: #fff;
border: 1px solid #e7e7e7;
}
.panel-tabs .tab-content > .panel {
margin: 0;
}
.panel-stack > .panel {
margin: 0;
}
.panel-tabs[x-sidebar] {
margin-bottom: 0;
}
.bar-layout .portlet-header .navbar-inner {
background-image: none;
background-color: #f5f5f5;
border-bottom: 1px solid #e7e7e7;
}
.bar-layout .portlet-header .divider-vertical {
border-left: 1px solid #D3D3D3;
}
.bar-layout .form-item-portlet,
.bar-layout .form-item-portlet .portlet-minimized {
height: auto;
min-height: inherit !important;
}
.panel-layout .m2o-editor {
position: relative;
}
.panel-layout .m2o-editor-controls {
position: absolute;
top: -18px;
right: 0;
}
.panel-layout .m2o-editor-controls a {
margin: 0 4px;
color: #333;
}
[x-show-title="false"] > .m2o-editor {
margin-top: 18px;
}
.panel-layout .o2m-list .form-item {
padding: 0;
}
.panel-layout .o2m-list .panel {
margin-bottom: 0;
}
.panel-layout .editable > .o2m-list {
display: table;
table-layout: fixed;
width: 100%;
}
.panel-layout .editable > .o2m-list .o2m-list-row {
display: table-row;
}
.panel-layout .editable > .o2m-list .o2m-list-row > div,
.panel-layout .editable > .o2m-list .o2m-list-row > span {
display: table-cell;
padding: 2px 0;
width: 100%;
}
.panel-layout .editable > .o2m-list table.form-layout {
table-layout: fixed;
}
.panel-layout .editable > .o2m-list .o2m-list-row > span.o2m-list-remove {
vertical-align: middle;
width: 24px;
}
.panel-layout .editable > .o2m-list .o2m-list-row > a,
.panel-layout .editable > .o2m-list .o2m-list-row > span.o2m-list-remove a {
color: #333333;
outline: none;
}
.panel-layout .editable > .o2m-list .o2m-list-row > span.o2m-list-remove a {
padding: 0 6px;
}
.panel-layout .editable > .o2m-list .o2m-list-row .toggle-item button {
border-left: 0;
border-top: 0;
border-right: 0;
outline: none;
height: 28px;
color: #ccc;
padding: 4px 6px;
}
.panel-layout .editable > .o2m-list .o2m-list-row .toggle-item button:focus {
color: #ccc;
background: none;
box-shadow: none;
border-color: #ccc;
}
.panel-layout .editable > .o2m-list .o2m-list-row .toggle-item button:hover,
.panel-layout .editable > .o2m-list .o2m-list-row .toggle-item button:active,
.panel-layout .editable > .o2m-list .o2m-list-row .toggle-item button.active {
color: #333;
background: none;
box-shadow: none;
border-color: #ccc;
}
.panel-layout .editable > .o2m-list .o2m-list-remove i,
.panel-layout .editable > .o2m-list .o2m-list-add i {
color: #ccc;
font-size: 12px;
}
.panel-layout .editable > .o2m-list .o2m-list-remove i:hover,
.panel-layout .editable > .o2m-list .o2m-list-add i:hover {
color: #333;
}
.panel-layout .row-fluid [class*="span"] {
min-height: inherit !important;
}
.panel-layout .form-item > a.btn,
.panel-layout .form-item-container button {
width: 100%;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 3px 10px 3px;
min-width: 28px;
height: 28px;
}
.panel-layout .btn:focus {
background-position: inherit;
}
.panel-layout .form-item.btn-group {
width: 100%;
}
.panel-layout .form-item.btn-group > a.btn {
width: auto;
}
.panel-layout .btn-link.disabled {
pointer-events: none;
cursor: default;
opacity: 0.6;
}
.panel-layout .row-fluid.has-labels > .form-item > a.btn,
.panel-layout .row-fluid.has-labels > .form-item > [x-show-title="false"][ng-model] {
margin-top: 18px;
}
.panel[x-stacked] > .panel-body > .panel-layout > .row-fluid.has-labels > .form-item > a.btn,
.panel[x-stacked] > .panel-body > .panel-layout > .row-fluid.has-labels > .form-item > [x-show-title="false"][ng-model] {
margin-top: 0;
}
.panel-tabs .nav-tabs-responsive {
margin-bottom: -1px;
display: inline-flex;
}
.panel-tabs .nav-tabs-responsive > li {
margin-top: -12px;
}
.panel-tabs .nav-tabs-responsive > li.dropdown {
max-width: 120px;
}
.panel-tabs .nav-tabs-responsive a,
.panel-tabs .nav-tabs-responsive > li.dropdown > a > span {
outline: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.panel-tabs .nav-tabs-responsive > li.dropdown > a > span {
display: inline-block;
max-width: 74px;
margin-bottom: -4px;
}
/** nested editor **/
.panel-layout .nested-editor .panel:not(.panel-related) {
margin-bottom: 16px;
}
.panel-layout .nested-editor .panel-layout .panel:not(.panel-related) {
margin-bottom: 0;
}
.panel-layout .nested-editor .panel .panel-body {
padding: 14px;
}
.panel-layout .nested-editor .panel-layout .panel .panel-body {
padding: 0;
}
.panel-layout .form-item-group.bordered-box {
padding: 14px 14px 0;
}
.panel-layout .form-item-group > legend {
padding: 0;
margin: 0;
}
.panel-layout .stackbar .navbar .navbar-inner {
background: #fff;
padding: 11px 15px 14px;
border-top: 4px solid #e7e7e7;
border-bottom: 1px solid #e7e7e7;
border-radius: 0;
}
.panel-layout .stackbar .navbar .navbar-inner .brand {
margin: 0;
padding: 0;
font-size: 14px;
font-weight: 600;
}
.panel-layout .stackbar .navbar .navbar-inner .icons-bar {
padding: 0;
}
.panel-layout .stackbar .navbar .navbar-inner .icons-bar i {
margin: 0 4px;
padding: inherit;
}
.panel-layout .stackbar > .slickgrid {
top: 47px;
}
.panel-layout .stackbar .form-item-group.inline-form {
padding-top: 30px;
}
.panel-json .bar-layout {
padding: 0;
}
/** some special classes */
.border-left-none,
.border-left-none.form-item-container,
.border-left-none.form-item-container > input {
border-left: none;
}
.border-right-none,
.border-left-none.form-item-container,
.border-left-none.form-item-container > input {
border-left: none;
}
.address-list .o2m-list-row {
width: 48.71%;
float: left;
margin-left: 0;
}
.address-list .o2m-list-add {
width: 100%;
margin-left: 0;
}
.address-list .o2m-list-row.even {
margin-left: 2.56%;
}
@media (max-width: 767px) {
.address-list .o2m-list-row {
width: 100%;
float: none;
margin-left: 0 !important;
}
}
.panel-layout .order-subtotal dl {
max-width: 280px;
float: right;
}
.panel-layout .order-subtotal dt,
.panel-layout .order-subtotal dd {
padding: 4px;
}
.panel-layout .order-subtotal dt {
padding-right: 8px;
font-weight: normal;
white-space: normal;
width: 55%;
}
.panel-layout .order-subtotal dd {
margin-left: 0;
text-align: right;
}
.panel-layout .order-subtotal dd:empty {
height: 26px;
}
.panel-layout .order-subtotal-total {
border-top: 1px solid #ccc;
font-size: 18px;
font-weight: 600;
}
@media (max-width: 767px) {
.panel-layout .order-subtotal dt {
float: left;
text-align: right;
width: 48%;
}
}
.panel.field-append > .panel-body > .panel-layout > .row-fluid {
display: flex;
}
.panel.field-append > .panel-body > .panel-layout > .row-fluid > .form-item[class*="span"] {
margin: 0;
width: auto;
float: inherit;
}
.panel.field-append > .panel-body > .panel-layout > .row-fluid > .form-item:first-child {
flex: 1;
}
.panel-flex {
display: flex;
flex-wrap: wrap;
flex-direction: row;
margin-left: -7px;
margin-right: -7px;
}
.panel-flex [class*="flex"] {
width: 100%;
margin-left: 7px;
margin-right: 7px;
}
.panel-flex .form-item-hidden {
display: none;
}
.panel-flex .flex1 {
width: calc(8.33% - 14px);
}
.panel-flex .flex2 {
width: calc(8.33% * 2 - 14px);
}
.panel-flex .flex3 {
width: calc(8.33% * 3 - 14px);
}
.panel-flex .flex4 {
width: calc(8.33% * 4 - 14px);
}
.panel-flex .flex5 {
width: calc(8.33% * 5 - 14px);
}
.panel-flex .flex6 {
width: calc(8.33% * 6 - 14px);
}
.panel-flex .flex7 {
width: calc(8.33% * 7 - 14px);
}
.panel-flex .flex8 {
width: calc(8.33% * 8 - 14px);
}
.panel-flex .flex9 {
width: calc(8.33% * 9 - 14px);
}
.panel-flex .flex10 {
width: calc(8.33% * 10 - 14px);
}
.panel-flex .flex11 {
width: calc(8.33% * 11 - 14px);
}
.panel-flex .flex12 {
width: calc(8.33% * 12 - 14px);
}
@media (max-width: 767px) {
.panel-flex {
flex-direction: column;
margin: 0;
}
.panel-flex [class*="flex"] {
width: 100%;
margin-left: 0;
margin-right: 0;
}
}
.help-item {
border-radius: 0;
}
.panel .help-item {
margin: 0;
}
.help-item:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\f10e";
margin-left: 6px;
}
.help-item:before {
font: normal normal normal 14px/1 FontAwesome;
content: "\f10d";
margin-right: 6px;
}
.info-button {
height: 60px;
width: 100%;
padding: 4px 8px;
border-style: dotted;
}
.info-button > i,
.info-button > img {
display: block;
width: 32px;
float: left;
}
.info-button > i {
height: 100%;
font-size: 26px;
line-height: 50px;
}
.info-button > img {
display: block;
height: 32px;
margin-top: 4px;
}
.info-button-data {
float: left;
width: calc(100% - 32px);
height: 100%;
line-height: 22px;
padding-left: 8px;
text-align: left;
}
.info-button-value {
font-size: 22px;
font-weight: 600;
display: block;
margin: 2px 0 4px;
white-space: nowrap;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
}
.info-button-title {
display: block;
overflow: hidden;
}
.info-button > i,
.info-button .info-button-value,
.info-button .info-button-title {
text-shadow: 1px 1px 0 rgba(0,0,0,.15);
}
.info-button:not([class*=btn-]) > i,
.info-button:not([class*=btn-]) .info-button-value {
color: #3c763d;
}
.info-button:not([class*=btn-]):hover,
.info-button:not([class*=btn-]):focus,
.info-button:not([class*=btn-]):active,
.info-button:not([class*=btn-]).active {
box-shadow: none;
background-color: #fff;
}

87
sophal/css/view.gantt.css Normal file
View File

@ -0,0 +1,87 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.view-container .gantt-view > div:last-child {
position: absolute;
top: 46px;
bottom: 0; left: 4px; right: 0;
}
.gantt_grid_head_cell {
font-weight: bold;
}
.gantt-main {
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
overflow: auto;
}
.gantt_time_selects > select {
width: auto;
}
.child_preview {
box-sizing: border-box;
margin-top: 2px;
position: absolute;
z-index: 1;
color: white;
text-align: center;
font-size: 12px;
}
.gantt_task_line.task-collapsed {
height: 4px;
opacity: 0.25;
}
.gantt_task_line.gantt_project.task-collapsed .gantt_task_content {
display: none;
}
.gantt_row.task-parent {
font-weight: bold;
}
.gantt_task_progress{
text-align:left;
padding-left:10px;
box-sizing: border-box;
color:white;
font-weight: bold;
}
.gantt_task_line.gantt_project{
color:white;
}
.gantt_side_content{
color:#333;
}
.summary-bar{
font-weight: bold;
}
.gantt_resource_task .gantt_task_content{
color:inherit;
}
.gantt_resource_task .gantt_task_progress{
background-color:rgba(33,33,33,0.3);
}

314
sophal/css/view.kanban.css Normal file
View File

@ -0,0 +1,314 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.cards-view,
.kanban-view {
position: absolute;
top: 41px;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
padding: 18px 9px;
}
.cards-view,
.cards-view *,
.kanban-view,
.kanban-view * {
box-sizing: border-box;
}
.kanban-view {
display: flex;
padding: 0 6px;
background-color: #fff;
}
.kanban-column {
display: flex;
flex-direction: column;
width: 24em;
background-color: #e7eaec;
margin: 12px 6px 0;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.kanban-column ul.kanban-card-list,
.kanban-column li.kanban-card {
list-style: none;
}
.kanban-column ul.kanban-card-list {
flex: 1;
margin: 0 4px 4px;
padding: 10px 10px 35px;
overflow: auto;
max-height: calc(100% - 22px);
background: #fff;
}
.kanban-column .ui-sortable-placeholder {
border: 1px dashed #cecece !important;
visibility: visible !important;
background: #e7eaec !important;
}
.kanban-column .ui-sortable-helper {
box-shadow: 0 5px 10px rgba(0,0,0,.2);
}
.kanban-column > h3 {
font-weight: 600;
font-size: 16px;
margin: 0;
padding: 10px;
line-height: 18px;
}
.kanban-column .input-group {
display: flex;
margin: 0 8px 6px;
}
.kanban-column .input-group .form-control {
height: 100%;
margin: 0;
flex: 1;
}
.kanban-column .input-group .input-group-btn > .btn {
margin-left: -1px;
}
.kanban-column .input-group .form-control:focus,
.kanban-column .input-group-btn > .btn:active,
.kanban-column .input-group-btn > .btn:focus,
.kanban-column .input-group-btn > .btn:hover {
z-index: 2;
}
.kanban-more {
border-bottom: 1px dashed #ddd;
text-align: center;
margin: -40px 20px 25px 20px;
}
.kanban-more .btn-load-more:hover {
background-color: #eaf2f8 !important;
color: #537c9f;
}
.kanban-more .btn-load-more {
position: relative;
top: 11px;
padding: 0 4px;
outline: none;
border-color: #8aafce;
border-radius: 4px !important;
border-bottom-width: 2px;
color: #537c9f;
}
.kanban-empty {
display: none;
margin: -30px 20px 10px 20px;
}
.kanban-column.empty .kanban-empty {
display: block;
}
.kanban-card {
list-style: none;
position: relative;
background: #fafafb;
border: 1px solid #e7eaec;
margin: 0 0 10px 0;
padding: 10px;
border-radius: 2px;
box-sizing: content-box;
border-left: 3px solid #e7eaec;
}
.kanban-card:hover {
background-color: #fff;
cursor: pointer;
}
.kanban-card.primary {
border-left: 3px solid #337ab7;
}
.kanban-card.warning {
border-left: 3px solid #f0ad4e;
}
.kanban-card.danger {
border-left: 3px solid #d9534f;
}
.kanban-card.info {
border-left: 3px solid #23c6c8;
}
.kanban-card.success {
border-left: 3px solid #0275d8;
}
.kanban-card h4 {
margin: 0 0 4px 0;
font-weight: 600;
font-size: 13px;
}
.kanban-card img {
width: 24px;
height: 24px;
position: absolute;
right: 2px;
bottom: 2px;
border-radius: 100%;
}
.rect-image .kanban-card img {
border-radius: 0;
}
.kanban-card .card-body {
}
.kanban-card .card-footer {
margin-top: 4px;
color: #777;
font-size: 12px;
}
.kanban-card-menu {
display: none;
position: absolute;
top: 2px;
right: 2px;
}
.kanban-card-menu > a {
padding: 0 4px;
outline: none !important;
border: 0 !important;
box-shadow: none !important;
}
.kanban-card:hover .kanban-card-menu,
.kanban-card .kanban-card-menu.open {
display: inline-block;
}
.cards-view .kanban-card-list {
display: flex;
flex-wrap: wrap;
}
.cards-view.cards-center .kanban-card-list {
justify-content: center;
}
.cards-view .kanban-card-container {
float: left;
width: 33.33333333%;
padding-left: 10px;
padding-right: 10px;
margin-bottom: 20px;
display: flex;
}
.cards-view .kanban-card {
box-sizing: border-box;
margin: 0;
padding: 16px;
background-color: #fff;
overflow: hidden;
width: 100%;
}
.cards-view .kanban-card address {
margin-bottom: 0;
}
.cards-view .kanban-card p {
margin: 9px 0;
}
.cards-view .card-image {
position: relative;
text-align: center;
}
.cards-view .card-image img {
position: relative;
width: 64px;
height: 64px;
}
.cards-view .card-image strong {
margin-top: 6px;
display: block;
}
.cards-view .cards-no-records {
display: none;
position: absolute;
top: 48%;
width: 100%;
font-weight: 400;
text-align: center;
}
.cards-view.empty .cards-no-records {
display: block;
}
.kanban-column h3,
.kanban-column h4,
.kanban-column .card-body,
.kanban-column .card-footer {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 767px) {
.cards-view {
padding: 12px 0;
}
.cards-view .kanban-card-container {
width: 100% !important;
margin-bottom: 12px;
}
}
@media (max-width: 480px) {
.kanban-view {
flex-direction: column;
padding-bottom: 12px;
}
.kanban-column {
display: block;
}
.kanban-column ul.kanban-card-list {
padding-bottom: 0;
}
}

394
sophal/css/view.mail.css Normal file
View File

@ -0,0 +1,394 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* thread view */
.mail-thread-view {
width: inherit !important;
max-width: 720px;
margin: 0 16px 0 4px !important;
padding: 0;
}
.mail-thread-view .panel {
border: none;
box-shadow: none;
background-color: transparent;
}
/* form widgets */
.mail-thread {
margin-top: 16px;
margin-left: 20px;
border-left: 2px solid #ddd;
}
.mail-composer {
margin: -16px 0 20px 30px;
}
.mail-composer textarea {
max-width: 100%;
margin-bottom: 0;
}
.mail-composer .btn {
margin-right: 8px;
}
.mail-composer-buttons {
padding-top: 8px;
}
.mail-composer-files {
padding-top: 8px;
}
.mail-composer-files ul {
margin: 0;
list-style: none;
}
.mail-editor .mail-files {
min-height: 18px;
}
.mail-files i {
margin-right: 4px;
}
.mail-files i.fa-close {
cursor: pointer;
}
.mail-thread .avatar {
position: relative;
display: inline-block;
width: 40px;
height: 40px;
margin-left: -21px;
border-radius: 500px;
line-height: 40px;
text-align: center;
font-size: 20px;
font-weight: 600;
color: #ffffff;
text-transform: uppercase;
text-shadow: 1px 1px 0 rgba(0,0,0,.15);
}
.mail-thread .avatar:not([class*=bg-]) {
background-color: #0275d8;
}
.mail-thread .avatar:hover,
.mail-thread .avatar:active,
.mail-thread .avatar:focus {
text-decoration: none;
}
.mail-thread .avatar img {
width: 100%;
height: 100%;
border-radius: 500px;
vertical-align: inherit;
}
.mail-thread .mail-message-indent {
padding-left: 40px;
}
.mail-thread .mail-message {
position: relative;
border-radius: 0;
margin-left: 30px;
margin-bottom: 20px;
border: 1px solid #ddd;
border-radius: 2px;
background-color: #fff;
box-shadow: 0 1px 1px rgba(0,0,0,0.05);
}
.mail-thread .mail-message > .arrow {
position: absolute;
display: inline-block;
width: 11px;
height: 11px;
border: 1px solid #ddd;
left: -7px;
top: 12px;
background-color: #fff;
border-right: 0;
border-bottom: 0;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.mail-thread .mail-message > .star {
position: absolute;
color: #d9534f;
left: 8px;
}
.mail-thread .mail-message > .star i {
font-size: 8px;
}
.mail-thread .mail-message-icons {
float: right;
margin: 10px;
}
.mail-thread .mail-message-icons i {
cursor: pointer;
margin-left: 4px;
color: #0088cc;
}
.mail-thread .mail-message-icons .btn {
padding: 0;
margin-top: -4px;
outline: none !important;
border: 0 !important;
box-shadow: none !important;
}
.mail-thread .mail-message-icons .btn i {
margin: 0;
padding: 4px;
}
.mail-thread .mail-message-body {
padding: 8px 16px;
}
.mail-thread .mail-message-footer {
font-size: smaller;
}
.mail-thread .mail-message-footer a {
cursor: pointer;
}
.mail-thread .mail-message-files {
margin: 4px 0 4px -4px;
}
.mail-thread .mail-message-files ul {
margin: 0;
}
.mail-thread-more {
border-bottom: 1px dashed #ddd;
text-align: center;
margin-top: -15px;
margin-left: -2px;
border-left: 2px solid #fff;
}
.mail-thread-more .btn-load-more:hover {
background-color: #eaf2f8 !important;
color: #537c9f;
}
.mail-thread-more .btn-load-more {
position: relative;
top: 10px;
padding: 0 4px;
outline: none;
border-color: #8aafce;
border-radius: 4px !important;
border-bottom-width: 2px;
color: #537c9f;
}
.mail-thread-view .mail-thread-more {
border-left: 2px solid #f3f3f4;
}
.mail-messages > div > .mail-composer:first-child {
margin: 0 0 20px 0;
}
.mail-followers .panel-title > span {
width: calc(100% - 40px);
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}
.mail-followers .panel-icons i {
margin-left: 8px;
cursor: pointer;
}
.mail-followers .links {
list-style: none;
margin: 0;
}
.mail-followers .links li {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mail-followers .links i.fa-remove {
cursor: pointer;
margin-top: 3px;
margin-right: 4px;
}
.mail-message i.fa:hover,
.mail-followers i.fa:hover {
color: black;
}
.mail-message .mail-message-header {
font-weight: 600;
padding: 10px 16px;
border-bottom: 1px solid #ddd;
}
.mail-message .mail-message-header .track-tags:before {
content: ' : ';
}
.mail-message .track-message {
font-weight: 600;
}
.mail-message .track-content {
margin-bottom: 8px;
}
.mail-message .track-fields {
margin: 0 0 4px 26px;
}
.mail-message .track-tags {
}
.mail-message .track-tags .label {
margin-right: 2px;
}
.mail-message-body a.show-full {
display: inline-block;
background-color: #f1f1f1;
border: 1px solid #ddd;
line-height: 8px;
outline: none;
padding: 1px 6px 0px;
margin-top: 6px;
}
.mail-message-body a.show-full:hover {
background-color: #d8d8d8;
border-color: #cdcdcd;
}
.mail-message-body a.show-full i {
line-height: 6px;
}
.form-mail .fade {
opacity: 0.1;
}
.form-mail .fadeIn {
opacity: 1;
-webkit-transition: all linear 0.5s;
transition: all linear 0.5s;
}
.form-mail .fadeDim .fade,
.form-mail .fadeDim .mail-thread-more {
display: none;
}
/** special views */
.mail-group-form,
.mail-group-list {
}
.mail-group-form {
padding-left: 20px;
padding-right: 20px;
}
.mail-group-form {
}
.grid-cards {
padding: 12px;
}
.grid-cards .grid-card {
padding: 6px;
margin: 4px;
border: 1px solid #c0c0c0;
border-radius: 0;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
width: calc(25% - 22px);
min-width: 300px;
}
@media (max-width: 768px) {
.grid-cards .grid-card {
width: calc(50% - 22px);
}
}
@media (max-width: 480px) {
.grid-cards .grid-card {
width: calc(100% - 22px);
}
}
.grid-cards .grid-card,
.grid-cards .grid-card-left,
.grid-cards .grid-card-right {
position: relative;
display: inline-block;
vertical-align: top;
}
.grid-cards .grid-card-left {
overflow: hidden;
border-collapse: separate;
}
.grid-cards .grid-card-left img {
width: 64px;
min-height: 64px;
padding: 2px;
}
.grid-cards .grid-card-right {
width: calc(100% - 86px);
min-height: 120px;
margin-left: 4px;
}
.grid-cards .grid-card-right .buttons {
margin-top: 4px;
}

374
sophal/css/view.popup.css Normal file
View File

@ -0,0 +1,374 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.ui-dialog {
padding: 0;
border: 0;
top: 30px !important;
left: 0 !important;
right: 0 !important;
display: flex;
flex-flow: column nowrap;
}
.ui-dialog .ui-dialog-titlebar {
border: none;
border-bottom: 1px solid #EEE;
border-radius: 0;
background: none;
padding: 9px 15px;
font-size: 20.5px;
font-weight: 600;
line-height: 26px;
flex: 0 0 auto;
}
.ui-dialog .ui-dialog-title {
margin: 0;
}
.ui-dialog,
.ui-dialog .ui-dialog-title {
border-radius: 0;
}
.ui-dialog .ui-dialog-titlebar-close.ui-state-hover {
background: none;
border: 0;
}
.ui-dialog .ui-dialog-titlebar-close:hover,
.ui-dialog .ui-dialog-titlebar-close:focus {
padding: 1px;
}
.ui-dialog .ui-dialog-titlebar-close .ui-icon,
.ui-dialog .ui-dialog-titlebar-close .ui-icon.ui-state-hover {
background: none !important;
}
.ui-dialog .ui-dialog-titlebar-close {
top: 20px;
right: .5em;
height: auto;
width: auto;
opacity: 0.2;
}
.ui-dialog .ui-dialog-titlebar-close.ui-state-hover {
border: 0;
background: transparent;
font-weight: 600;
opacity: 0.4;
}
.ui-dialog .ui-dialog-titlebar-close span::before {
content: "\00D7";
display: inline-block;
}
.ui-dialog .ui-dialog-titlebar-close .ui-icon {
text-indent: 0px !important;
line-height: 20px;
font-size: 20px;
height: 20px;
width: 12px;
}
.ui-dialog .ui-dialog-titlebar-close.ui-state-focus {
background: inherit;
border: inherit;
font-weight: inherit;
}
.ui-dialog .ui-dialog-titlebar-max,
.ui-dialog .ui-dialog-titlebar-collapse {
position: absolute;
right: 1.6em;
top: 7px;
opacity: 0.2;
}
.ui-dialog .ui-dialog-titlebar-max:hover,
.ui-dialog .ui-dialog-titlebar-collapse:hover {
font-weight: bold;
color: #212121;
opacity: 0.4;
}
.ui-dialog .ui-dialog-titlebar-collapse {
right: 2.6em;
}
.ui-dialog .ui-dialog-titlebar-close span::before {
font-size: 24px;
}
.ui-dialog-buttonpane {
border-top: 1px solid #DDD;
}
.ui-dialog-buttonpane .btn {
padding: 16px auto;
}
.ui-dialog-buttonpane .btn[disabled] {
cursor: inherit;
}
.ui-dialog .ui-dialog-content {
padding: 4px;
min-height: 50px;
}
.ui-dialog .ui-dialog-content:not(.ui-dialog-ie11) {
max-height: calc(100% - 45px - 52px) !important;
height: inherit !important;
}
.ui-dialog .ui-dialog-content[ui-editor-popup] {
min-height: 200px !important;
}
.ui-dialog .ui-dialog-content[ui-selector-popup] {
overflow: hidden;
height: 450px !important;
}
.ui-widget-overlay {
background: none;
background-color: black;
}
.ui-dialog .ui-dialog-buttonpane {
margin-top: 0;
padding: .3em .5em .5em .5em;
flex: 0 0 auto;
}
.ui-dialog.collapsed .ui-dialog-content,
.ui-dialog.collapsed .ui-dialog-buttonpane {
display: none !important;
}
.ui-dialog-buttonpane .btn-group > .btn {
margin: 0;
}
.ui-dialog-buttonpane .btn-group > .btn:first-child {
margin-left: 0;
}
.ui-dialog-buttonpane .btn-group > .btn:last-child {
margin-left: -1px;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset-left button {
margin-left: 0.4em;
}
.ui-dialog-titlebar .record-pager {
position: absolute;
right: 76px;
top: 8px;
line-height: 25px;
}
.ui-dialog-titlebar .record-pager-text {
margin-top: 0;
}
.no-close .ui-dialog-titlebar-close {
display: none;
}
.ui-dialog .message-box a {
color: #0088cc;
outline: none;
}
.ui-dialog form[ui-bar-layout] {
padding-bottom: 0px;
}
.ui-dialog-content > [ui-view-form],
.ui-dialog-content > [ui-view-form] > form {
box-sizing: border-box;
width: 100%;
min-width: 100%;
max-width: 100%;
}
.ui-dialog-responsive {
position: absolute;
width: 100% !important;
margin: 0 auto !important;
max-width: calc(100% - 16px) !important;
max-height: calc(100% - 64px) !important;
border: none;
}
.ui-dialog-small .ui-dialog-content {
height: auto !important;
overflow: inherit;
}
.ui-dialog-small .ui-dialog-buttonpane {
position: relative;
}
@media screen and (min-width: 768px) {
.ui-dialog-responsive {
width: 740px !important;
}
.ui-dialog-small {
width: 480px !important;
}
}
@media screen and (min-width: 1024px) {
.ui-dialog-responsive {
width: 900px !important;
}
.ui-dialog-small {
width: 480px !important;
}
}
@media screen and (max-width: 768px) {
.ui-dialog .ui-dialog-titlebar,
.ui-dialog .ui-dialog-buttonpane {
position: absolute;
left: 0;
right: 0;
}
.ui-dialog {
position: absolute;
margin: 8px !important;
top: 0 !important;
left: 0 !important;
width: calc(100% - 16px) !important;
height: calc(100% - 16px) !important;
max-height: calc(100% - 16px) !important;
}
.ui-dialog .ui-dialog-title {
top: 0;
}
.ui-dialog .ui-dialog-buttonpane {
bottom: 0;
}
.ui-dialog .ui-dialog-content {
box-sizing: border-box;
width: 100% !important;
height: calc(100% - 45px - 52px) !important;
overflow: auto;
margin-top: 45px;
}
.ui-dialog .ui-dialog-content[ui-selector-popup] {
min-height: inherit !important;
}
.ui-dialog-small {
height: auto !important;
}
.ui-dialog-small .ui-dialog-content {
height: auto !important;
}
.ui-dialog-small .ui-dialog-buttonpane {
position: relative;
}
}
.ui-dialog.maximized {
top: 0 !important;
left: 0 !important;
margin: 8px !important;
width: calc(100% - 16px) !important;
height: calc(100% - 16px) !important;
max-height: calc(100% - 16px) !important;
}
.ui-dialog.maximized .ui-dialog-buttonpane {
bottom: 0;
}
.ui-dialog.maximized .ui-dialog-content {
width: auto !important;
height: calc(100% - 45px - 52px) !important;
}
.ui-dialog.maximized.collapsed {
height: auto !important;
}
.ui-dialog .translation-form {
overflow: auto;
}
.ui-dialog .translation-form i {
cursor: pointer;
}
.ui-dialog .translation-form i.fa-times {
line-height: 28px;
margin-right: -12px;
float: right;
}
.ui-dialog .translation-form i.fa-plus {
margin-right: 4px;
}
.ui-dialog .translation-form form {
margin: 0;
margin-right: 8px;
}
.ui-dialog .translation-form form label {
font-weight: 600;
min-height: 0;
margin-left: 6px;
}
.ui-dialog .translation-form form input {
margin-left: 6px;
}
.ui-dialog .translation-form form hr {
margin: 8px 0;
}
#loginWindow * {
box-sizing: border-box;
}
#loginWindow {
padding: 16px;
}
#loginWindow form {
margin-bottom: 0;
}
#loginWindow input {
width: 100%;
height: 33px;
}
#errorWindow .errorMessage,
#errorWindow .errorDetails {
margin: 8px;
}
#errorWindow .errorDetails .nav-tabs,
#errorWindow .errorDetails pre {
margin-bottom: 0;
}
#errorWindow .errorDetails .tab-contents {
padding: 4px;
border: 1px solid #d4d4d4;
border-top: none;
}

412
sophal/css/view.portal.css Normal file
View File

@ -0,0 +1,412 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.portal-view .portal,
.portal-view .portlet,
.portal-view .portlet-body,
.portal-view .portlet-header,
.portal-view .portlet-content {
overflow: hidden;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.form-view .portlet,
.form-view .portlet-body,
.form-view .portlet-header,
.form-view .portlet-content {
overflow: hidden;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.portal-view .portal {
padding: 4px;
}
.portal > .portal-tabs,
.portal > .portlet {
position: relative;
float: left;
}
.portlet-header.navbar i {
padding: 4px;
margin: 0;
}
.portlet-header.navbar .portlet-pager i.disabled {
color: gray;
cursor: default;
}
.portlet-header.navbar .divider-vertical {
height: 28px;
}
.portlet .portlet-body,
.portlet .portlet-content {
position: absolute !important;
}
.portlet .portlet-body {
top: 0px;
right: 2px;
bottom: 4px;
left: 2px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.portlet .portlet-content {
top: 30px;
left: 0px;
right: 0px;
bottom: 0px;
border: 1px solid #d4d4d4;
border-top: none;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.portlet .portlet-content > div,
.portlet .portlet-content > div > .slickgrid {
top: -1px !important;
height: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
border: none;
}
.portal-tabs {
margin-bottom: 4px;
}
.portal-tabs > .nav-tabs {
margin-bottom: 0;
}
.portal-tabs > .tab-content {
position: relative;
border: 1px solid #DDD;
border-top: 0;
background-color: #fff;
}
.portlet .portlet-content > .nvtooltip {
height: auto;
}
.portlet.portlet-minimized {
height: 34px !important;
}
.portlet.portlet-minimized .portlet-content {
display: none !important;
}
.portal-view .ui-sortable-placeholder {
border: 2px dashed #c9c9c9;
visibility: visible !important;
margin-bottom: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
/** dashboard */
.dashboard {
margin: auto;
padding: 14px;
}
.dashboard.attached {
margin-top: -16px;
}
.dashboard,
.dashboard .dashlet {
box-sizing: border-box;
}
.dashboard .ui-sortable-placeholder {
border: 1px dashed #cecece !important;
visibility: visible !important;
background: #e7eaec !important;
}
.dashboard .ui-sortable-helper {
box-shadow: 0 5px 10px rgba(0,0,0,.3);
}
.dashboard .dashlet {
background-color: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.dashboard .dashlet-header {
border-top: 4px solid #e7e7e7;
padding: 11px 15px 14px;
margin-top: -1px;
cursor: move;
}
.bar-layout .dashlet {
height: 100%;
}
.bar-layout .dashlet-header {
cursor: default;
}
.bar-layout .panel .dashboard {
margin-bottom: 0;
}
.dashboard .dashlet-title {
font-size: 14px;
font-weight: 600;
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dashlet-title {
min-height: 14px;
}
.dashboard .dashlet-body {
border-top: 1px solid #e7e7e7;
height: 250px;
overflow: auto;
}
.dashboard .dashlet-body > * {
height: calc(100% - 1px);
}
.dashboard .dashlet-body .slickgrid {
height: 100%;
border: 0;
}
.dashboard .dashlet-body .slickgrid .slick-header {
border-top: 0;
}
.dashboard .dashlet-pager {
margin-right: 8px;
}
.dashboard ul.dashlet-buttons {
list-style: none;
margin: 0;
}
.dashboard ul.dashlet-buttons > li {
list-style: none;
float: left;
margin: 0 4px;
}
.dashboard ul.dashlet-buttons > li > a {
color: #333;
}
.dashboard .dashlet-buttons > a,
.dashboard .dashlet-pager > a {
color: #333;
padding-left: 8px;
}
.dashboard .dashlet-pager > a.disabled {
color: gray;
cursor: default;
}
.dashlet .chart-controls {
}
.dashlet .chart-controls form {
margin: 0;
padding: 0;
}
.dashlet .chart-controls .panel {
border: 0;
box-shadow: none;
margin: 0;
}
.dashlet .chart-controls .panel-body {
padding: 4px;
}
.dashboard .dashlet {
margin: 10px !important;
}
.form-item .dashboard {
padding: 0 !important;
}
.form-item .dashlet {
margin: 0 !important;
border: 1px solid #e7e7e7;
}
.form-item .dashlet .dashlet-body > * {
height: 100%;
}
.form-item .dashlet .dashlet-body {
padding: 0;
}
.form-item .dashlet .dashlet-body .portlet-grid .slickgrid {
border: 0;
}
.form-item .dashlet .dashlet-body .portlet-grid .slick-header {
border-top: 0;
}
/** responsive dashlet classes */
[class*="dashlet-cs"] {
float: left;
min-height: 1px;
}
.dashlet-cs12 {
width: calc(100% - 24px);
}
.dashlet-cs11 {
width: calc(91.666666667% - 24px);
}
.dashlet-cs10 {
width: calc(83.333333333% - 24px);
}
.dashlet-cs9 {
width: calc(75% - 24px);
}
.dashlet-cs8 {
width: calc(66.666666667% - 24px);
}
.dashlet-cs7 {
width: calc(58.333333333% - 24px);
}
.dashlet-cs6 {
width: calc(50% - 24px);
}
.dashlet-cs5 {
width: calc(41.666666667% - 24px);
}
.dashlet-cs4 {
width: calc(33.333333333% - 24px);
}
.dashlet-cs3 {
width: calc(25% - 24px);
}
.dashlet-cs2 {
width: calc(16.666666667% - 24px);
}
.dashlet-cs1 {
width: calc(8.333333333% - 24px);
}
@media (max-width: 768px) {
[class*="dashlet-cs"] {
width: calc(100% - 24px);
}
}
.chart-controls .span6 {
width: calc(50% - 12px) !important;
margin: 0 12px !important;
float: left !important;
}
.chart-controls .span6:first-child {
margin-left: 0 !important;
}
.chart-controls .span6:last-child {
margin-right: 0 !important;
}
.report-box {
position: relative;
}
.report-box .dashlet-body {
height: 60px;
padding: 15px 20px 20px 20px;
overflow: hidden;
}
.report-box .dashlet-body h1 {
margin: 0;
font-size: 30px;
font-weight: 100;
}
.report-box .report-tags {
position: absolute;
top: 12px;
right: 36px;
}
.report-table tfoot td {
border-top: 1px solid #ccc;
font-weight: 600;
}
@media (max-width: 480px) {
.nv-controlsWrap {
display: none !important;
}
.dashboard {
padding: 6px !important;
}
.dashboard .dashlet {
margin: 6px !important
}
.dashboard .dashlet:last-child {
margin-bottom: 12px !important;
}
[class*="dashlet-cs"] {
width: calc(100% - 12px);
}
}

109
sophal/css/view.tree.css Normal file
View File

@ -0,0 +1,109 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.view-container .tree-view > .tree-view-container {
position: absolute;
top: 40px;
bottom: 0; left: 0; right: 0;
border-top: 1px solid #e7e7e7;
background-color: #fff;
}
table.treetable {
border: 0;
margin: 0;
}
table.treetable tr {
line-height: 18px;
}
table.treetable tbody tr.branch {
background-color: #fff;
}
table.treetable tbody tr.selected {
background-color: #337ab7;
}
table.treetable tbody tr.selected a {
color: #fff;
}
table.treetable .tree-button i,
table.treetable .tree-button img {
margin-right: 4px;
}
table.treetable tbody tr.accept {
background-color: #a3bce4;
color: #fff
}
.tree-view-container .tree-header {
height: 24px;
color: #333;
background-color: #fff;
}
.tree-view-container .tree-header {
width: 100%;
white-space: nowrap;
border-bottom: 1px solid #e7e7e7;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.tree-view-container .tree-header th {
display: inline-block;
height: 100%;
padding: 4px;
padding-left: 6px;
border-left: 1px solid #e7e7e7;
text-align: left;
text-overflow: ellipsis;
overflow: hidden;
font-weight: 600;
}
.tree-view-container .tree-header th:first-of-type {
border-left: none;
}
.tree-view-container .tree-table {
overflow: auto;
position: absolute;
top: 27px; left: 0; right: 0; bottom: 0;
}
.tree-header th,
.tree-table td,
.tree-table td span {
box-sizing: content-box;
}
.dashlet .tree-view-container {
position: relative;
height: 100%;
border: 1px solid #d4d4d4;
}
.form-item .dashlet .tree-view-container {
border: 0;
}

206
sophal/css/wysiwyg.css Normal file
View File

@ -0,0 +1,206 @@
/**
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.wysiwyg-container {
border-radius: 0;
}
.wysiwyg-editor {
min-height: 100px;
}
.wysiwyg-browse {
padding: 0;
}
.wysiwyg-popup {
border-radius: 0;
background: #fff;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
background-clip: padding-box;
width: auto !important;
}
.wysiwyg-toolbar-icon {
padding: 6px 8px;
font-size: 14px;
}
.wysiwyg-toolbar a,
.wysiwyg-toolbar a:hover,
.wysiwyg-toolbar a:link,
.wysiwyg-toolbar a:visited {
text-decoration: none;
}
.wysiwyg-plugin-list {
overflow: hidden;
}
.wysiwyg-plugin-list a,
.wysiwyg-plugin-list a:link,
.wysiwyg-plugin-list a:visited {
display: block;
color: black;
padding: 5px 10px;
text-decoration: none;
cursor: pointer;
}
.wysiwyg-toolbar-icon:hover,
.wysiwyg-plugin-list a:hover {
color: #fff;
background-color: #5377b1;
}
.wysiwyg-toolbar-icon.disabled {
opacity: 0.3;
color: inherit;
background-color: inherit;
cursor: default;
}
.wysiwyg-popup.wysiwyg-popuphover {
width: auto;
}
input.wysiwyg-input {
margin: 0;
}
.wysiwyg-toolbar-divider {
background-color: #ddd;
display: inline-block;
height: 26px;
margin-left: 0px;
margin-right: 0px;
vertical-align: middle;
width: 1px;
}
/** normalize bootstrap css for content */
.html-item .html-content blockquote {
margin: 0 0 9px 40px;
border: none;
padding: 0px;
}
/** force same style on chrome */
.html-item .html-content blockquote[style*='margin: 0 0 0 40px'] {
margin: 0 0 9px 40px !important;
}
.html-item .html-content blockquote p {
font-size: 13.25px;
font-weight: inherit;
line-height: 18px;
}
.wysiwyg-plugin-list h1, h2, h3, h4, h5, h6,
.html-item .html-content h1, h2, h3, h4, h5, h6 {
font-weight: 600;
}
.wysiwyg-plugin-list h1,
.html-item .html-content h1 {
font-size: 24.75px;
}
.wysiwyg-plugin-list h2,
.html-item .html-content h2 {
font-size: 22.25px;
}
.wysiwyg-plugin-list h3,
.html-item .html-content h3 {
font-size: 18.75px;
}
.html-item textarea.html-content {
border: none;
width: 100%;
margin: 0;
}
.wysiwyg-plugin-list h1,
.wysiwyg-plugin-list h2,
.wysiwyg-plugin-list h3,
.wysiwyg-plugin-list h4,
.wysiwyg-plugin-list h5,
.wysiwyg-plugin-list h6,
.wysiwyg-plugin-list p,
.wysiwyg-plugin-list pre,
.wysiwyg-plugin-list blockquote {
margin: 0;
}
.wysiwyg-plugin-list pre {
padding: 2px 4px 0;
}
.html-item textarea.html-content {
border: none;
width: 100%;
margin: 0;
}
.wysiwyg-container.ui-resizable {
min-height: 100px;
}
.wysiwyg-container.ui-resizable .wysiwyg-editor {
min-height: auto;
}
.wysiwyg-container.ui-resizable textarea {
resize: none;
}
.wysiwyg-container.ui-resizable .html-content {
position: absolute;
top: 27px;
left: 0;
bottom: 4px;
right: 0;
}
.wysiwyg-container.ui-resizable .ui-resizable-handle.ui-resizable-s {
background-color: #cccccc;
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 4px;
}
.slick-external-editor.html-item {
background-color: white;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
min-width: 380px;
}
.slick-external-editor.html-item input.wysiwyg-input {
box-sizing: border-box;
}
.slick-external-editor .wysiwyg-editor {
max-height: 200px;
}
.slick-external-editor .wysiwyg-container textarea {
resize: none;
}

133
sophal/js/application.js Normal file
View File

@ -0,0 +1,133 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//= lib/underscore/underscore.js
//= lib/underscore/underscore.string.js
//= lib/moment/moment.js
//= lib/jquery.ui/js/jquery.js
//= lib/jquery.ui/js/jquery-ui.js
//= lib/jquery.ui/js/jquery.ui.mask.js
//= lib/bootstrap/js/bootstrap.js
//= lib/jquery.timepicker/jquery-ui-timepicker-addon.js
//= lib/slickgrid/lib/jquery.event.drag-2.2.js
//= lib/slickgrid/lib/jquery.event.drop-2.2.js
//= lib/slickgrid/slick.core.js
//= lib/slickgrid/slick.grid.js
//= lib/slickgrid/slick.dataview.js
//= lib/slickgrid/slick.groupitemmetadataprovider.js
//= lib/slickgrid/plugins/slick.headermenu.js
//= lib/slickgrid/plugins/slick.rowselectionmodel.js
//= lib/slickgrid/plugins/slick.checkboxselectcolumn.js
//= lib/slickgrid/plugins/slick.rowmovemanager.js
//= lib/jquery.treetable/js/jquery.treetable.js
//= lib/fullcalendar/fullcalendar.js
//= lib/fullcalendar/gcal.js
//= lib/dhtmlxGantt/dhtmlxgantt.js
//= lib/handsontable/handsontable.full.js
//= lib/d3/d3.js
//= lib/d3/nv/nv.d3.js
//= lib/d3/radar/radar-chart.js
//= lib/d3/gauge/gauge-chart.js
//= lib/d3/funnel/d3-funnel.js
//= lib/codemirror/codemirror.min.js
//= lib/wysiwyg/wysiwyg.js
//= lib/wysiwyg/wysiwyg-editor.js
//= lib/angular/angular.js
//= lib/angular/angular-route.js
//= lib/angular/angular-resource.js
//= lib/angular/angular-sanitize.js
//= lib/angular/angular-animate.js
//= lib/angular-ui/sortable.js
//= js/axelor.ns.js
//= js/axelor.ng.js
//= js/lib/i18n.js
//= js/lib/utils.js
//= js/lib/dialogs.js
//= js/lib/tabs.js
//= js/axelor.auth.js
//= js/axelor.app.js
//= js/axelor.ds.js
//= js/axelor.data.js
//= js/axelor.ui.js
//= js/axelor.nav.js
//= js/axelor.prefs.js
//= js/widget/widget.navtabs.js
//= js/widget/widget.navtree.js
//= js/widget/widget.navmenu.js
//= js/widget/widget.slickgrid.js
//= js/widget/widget.dialog.js
//= js/widget/widget.update.js
//= js/widget/widget.search.js
//= js/widget/widget.menubar.js
//= js/form/form.base.js
//= js/form/form.converters.js
//= js/form/form.actions.js
//= js/form/form.widget.js
//= js/form/form.layout.js
//= js/form/form.container.js
//= js/form/form.input.static.js
//= js/form/form.input.boolean.js
//= js/form/form.input.text.js
//= js/form/form.input.html.js
//= js/form/form.input.number.js
//= js/form/form.input.datetime.js
//= js/form/form.input.select.js
//= js/form/form.input.progress.js
//= js/form/form.input.binary.js
//= js/form/form.input.spreadsheet.js
//= js/form/form.relational.base.js
//= js/form/form.relational.single.js
//= js/form/form.relational.multiple.js
//= js/form/form.relational.nested.js
//= js/form/form.input.json.js
//= js/form/form.mail.js
//= js/form/form.code.js
//= js/view/view.base.js
//= js/view/view.form.js
//= js/view/view.grid.js
//= js/view/view.tree.js
//= js/view/view.html.js
//= js/view/view.search.js
//= js/view/view.portal.js
//= js/view/view.dashboard.js
//= js/view/view.popup.js
//= js/view/view.chart.js
//= js/view/view.calendar.js
//= js/view/view.gantt.js
//= js/view/view.kanban.js
//= js/view/view.custom.js
//= js/view/view.dms.js

View File

@ -0,0 +1,20 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//= lib/jquery.ui/js/jquery.js
//= lib/bootstrap/js/bootstrap.js
//= js/axelor.ns.js

688
sophal/js/axelor.app.js Normal file
View File

@ -0,0 +1,688 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Application Module
*
*/
(function() {
"use strict";
var loadingElem = null,
loadingTimer = null,
loadingCounter = 0;
function updateLoadingCounter(val) {
loadingCounter += val;
loadingCounter = Math.max(0, loadingCounter);
}
function hideLoading() {
if (loadingTimer) {
clearTimeout(loadingTimer);
loadingTimer = null;
}
if (loadingCounter > 0) {
loadingTimer = _.delay(hideLoading, 500);
return;
}
loadingTimer = _.delay(function () {
loadingTimer = null;
if (loadingElem) {
loadingElem.fadeOut(100);
}
}, 500);
}
function onHttpStart() {
updateLoadingCounter(1);
if (loadingTimer) {
clearTimeout(loadingTimer);
loadingTimer = null;
}
if (loadingElem === null) {
loadingElem = $('<div><span class="label label-important loading-counter">' + _t('Loading') + '...</span></div>')
.css({
position: 'fixed',
top: 0,
width: '100%',
'text-align': 'center',
'z-index': 2000
}).appendTo('body');
}
loadingElem.show();
}
function onHttpStop() {
updateLoadingCounter(-1);
hideLoading();
}
axelor.$evalScope = function (scope) {
var evalScope = scope.$new(true);
function isValid(name) {
if (!name) {
if (_.isFunction(scope.isValid)) {
return scope.isValid();
}
return scope.isValid === undefined || scope.isValid;
}
var ctrl = scope.form;
if (ctrl) {
ctrl = ctrl[name];
}
if (ctrl) {
return ctrl.$valid;
}
return true;
}
evalScope.$get = function(n) {
var context = this.$context || this.record || {};
if (context.hasOwnProperty(n)) {
return context[n];
}
return evalScope.$eval(n, context);
};
evalScope.$moment = function(d) { return moment(d); }; // moment.js helper
evalScope.$number = function(d) { return +d; }; // number helper
evalScope.$popup = function() { return scope._isPopup; }; // popup detect
evalScope.$iif = function(c, t, f) {
console.warn('Use ternary operator instead of $iif() helper.');
return c ? t : f;
};
evalScope.$sum = function (items, field, operation, field2) {
var total = 0;
if (items && items.length) {
items.forEach(function (item) {
var value = 0;
var value2 = 0;
if (field in item) {
value = +(item[field] || 0);
}
if (operation && field2 && field2 in item) {
value2 = +(item[field2] || 0);
switch (operation) {
case '*':
value = value * value2;
break;
case '/':
value = value2 ? value / value2 : value;
break;
case '+':
value = value + value2;
break;
case '-':
value = value - value2;
break;
}
}
if (value) {
total += value;
}
});
}
return total;
};
// current user and group
evalScope.$user = axelor.config['user.login'];
evalScope.$group = axelor.config['user.group'];
evalScope.$userId = axelor.config['user.id'];
evalScope.$contains = function(iter, item) {
if (iter && iter.indexOf)
return iter.indexOf(item) > -1;
return false;
};
// access json field values
evalScope.$json = function (name) {
var value = (scope.record || {})[name];
if (value) {
return angular.fromJson(value);
}
};
evalScope.$readonly = scope.isReadonly ? _.bind(scope.isReadonly, scope) : angular.noop;
evalScope.$required = scope.isRequired ? _.bind(scope.isRequired, scope) : angular.noop;
evalScope.$valid = function(name) {
return isValid(scope, name);
};
evalScope.$invalid = function(name) {
return !isValid(scope, name);
};
return evalScope;
};
axelor.$eval = function (scope, expr, context) {
if (!scope || !expr) {
return null;
}
var evalScope = axelor.$evalScope(scope);
evalScope.$context = context;
try {
return evalScope.$eval(expr, context);
} finally {
evalScope.$destroy();
evalScope = null;
}
};
axelor.$adjustSize = _.debounce(function () {
$(document).trigger('adjust:size');
}, 100);
var module = angular.module('axelor.app', ['axelor.ng', 'axelor.ds', 'axelor.ui', 'axelor.auth']);
module.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
var tabResource = {
action: 'main.tab',
controller: 'TabCtrl',
template: "<span><!-- dummy template --></span>"
};
$routeProvider
.when('/preferences', { action: 'preferences' })
.when('/about', { action: 'about' })
.when('/system', { action: 'system' })
.when('/', { action: 'main' })
.when('/ds/:resource', tabResource)
.when('/ds/:resource/:mode', tabResource)
.when('/ds/:resource/:mode/:state', tabResource)
.otherwise({ redirectTo: '/' });
}]);
module.config(['$httpProvider', function(provider) {
provider.useApplyAsync(true);
var toString = Object.prototype.toString;
function isFile(obj) {
return toString.call(obj) === '[object File]';
}
function isFormData(obj) {
return toString.call(obj) === '[object FormData]';
}
function isBlob(obj) {
return toString.call(obj) === '[object Blob]';
}
// restore old behavior
// breaking change (https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251)
function jsonReplacer(key, value) {
if (typeof key === 'string' && key.charAt(0) === '$') {
return undefined;
}
return value;
}
function transformRequest(d) {
return angular.isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? JSON.stringify(d, jsonReplacer) : d;
}
provider.interceptors.push('httpIndicator');
provider.defaults.transformRequest.unshift(transformRequest);
provider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
provider.useApplyAsync(true);
}]);
// only enable animation on element with ng-animate css class
module.config(['$animateProvider', function($animateProvider) {
$animateProvider.classNameFilter(/x-animate/);
}]);
module.factory('httpIndicator', ['$rootScope', '$q', function($rootScope, $q){
var doc = $(document);
var body = $('body');
var blocker = $('<div class="blocker-overlay"></div>')
.appendTo('body')
.hide()
.css({
position: 'absolute',
zIndex: 100000,
width: '100%', height: '100%'
});
var spinner = $('<div class="blocker-wait"></div>')
.append('<div class="blocker-spinner"><i class="fa fa-spinner fa-spin"></div>')
.append('<div class="blocker-message">' + _t('Please wait...') + '</div>')
.appendTo(blocker);
var blocked = false;
var blockedCounter = 0;
var blockedTimer = null;
var spinnerTime = 0;
function block(callback) {
if (blocked) return true;
if (blockedTimer) { clearTimeout(blockedTimer); blockedTimer = null; }
if (loadingCounter > 0 || blockedCounter > 0) {
blocked = true;
doc.on("keydown.blockui mousedown.blockui", function(e) {
if ($('#loginWindow').is(':visible')) {
return;
}
e.preventDefault();
e.stopPropagation();
});
body.css("cursor", "wait");
blocker.show();
}
unblock(callback);
return blocked;
}
function unblock(callback) {
if (blockedTimer) { clearTimeout(blockedTimer); blockedTimer = null; }
if (loadingCounter > 0 || blockedCounter > 0 || loadingTimer) {
if (spinnerTime === 0) {
spinnerTime = moment();
}
// show spinner after 5 seconds
if (moment().diff(spinnerTime, "seconds") > 5) {
blocker.addClass('wait');
}
if (blockedCounter > 0) {
blockedCounter = blockedCounter - 10;
}
blockedTimer = _.delay(unblock, 200, callback);
return;
}
doc.off("keydown.blockui mousedown.blockui");
body.css("cursor", "");
blocker.removeClass('wait').hide();
spinnerTime = 0;
if (callback) {
callback(blocked);
}
blocked = false;
}
axelor.blockUI = function(callback, minimum) {
if (minimum && minimum > blockedCounter) {
blockedCounter = Math.max(0, blockedCounter);
blockedCounter = Math.max(minimum, blockedCounter);
}
return block(callback);
};
axelor.unblockUI = function() {
return unblock();
};
function notSilent(config) {
return config && !config.silent;
}
return {
request: function(config) {
if (notSilent(config)) {
onHttpStart();
}
return config;
},
response: function(response) {
if (notSilent(response.config)) {
onHttpStop();
}
if (response.data) {
if (response.data.status === -1) { // STATUS_FAILURE
if (notSilent(response.config)) $rootScope.$broadcast('event:http-error', response.data);
return $q.reject(response);
}
if (response.data.status === -7) { // STATUS_LOGIN_REQUIRED
if (axelor.config['auth.central.client']) {
// redirect to central login page
window.location.href = './?client_name=' + axelor.config['auth.central.client']
+ "&hash_location=" + encodeURIComponent(window.location.hash);
} else if (notSilent(response.config)) {
// ajax login
$rootScope.$broadcast('event:auth-loginRequired', response.data);
}
return $q.reject(response);
}
}
return response;
},
responseError: function(error) {
if (notSilent(error.config)) {
onHttpStop();
$rootScope.$broadcast('event:http-error', error);
}
return $q.reject(error);
}
};
}]);
module.filter('unaccent', function() {
var source = 'ąàáäâãåæăćčĉęèéëêĝĥìíïîĵłľńňòóöőôõðøśșşšŝťțţŭùúüűûñÿýçżźž';
var target = 'aaaaaaaaaccceeeeeghiiiijllnnoooooooossssstttuuuuuunyyczzz';
source += source.toUpperCase();
target += target.toUpperCase();
var unaccent = function (text) {
return typeof(text) !== 'string' ? text : text.replace(/.{1}/g, function(c) {
var i = source.indexOf(c);
return i === -1 ? c : target[i];
});
};
return function(input) {
return unaccent(input);
};
});
module.filter('t', function(){
return function(input) {
var t = _t || angular.nop;
return t(input);
};
});
module.directive('translate', function(){
return function(scope, element, attrs) {
var t = _t || angular.nop;
setTimeout(function(){
element.html(t(element.text()));
});
};
});
module.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$rootScope', '$exceptionHandler', '$scope', '$http', '$route', 'authService', 'MessageService', 'NavService'];
function AppCtrl($rootScope, $exceptionHandler, $scope, $http, $route, authService, MessageService, NavService) {
function fetchConfig() {
return $http.get('ws/app/info').then(function(response) {
var config = _.extend(axelor.config, response.data);
$scope.$user.id = config["user.id"];
$scope.$user.name = config["user.name"];
$scope.$user.image = config["user.image"];
config.DEV = config['application.mode'] == 'dev';
config.PROD = config['application.mode'] == 'prod';
if (config['view.confirm.yes-no'] === true) {
_.extend(axelor.dialogs.config, {
yesNo: true
});
}
});
}
function openHomeTab() {
var path = $scope.routePath;
var homeAction = axelor.config["user.action"];
if (!homeAction || _.last(path) !== "main") {
return;
}
NavService.openTabByName(homeAction, {
__tab_prepend: true,
__tab_closable: false
});
}
// load app config
fetchConfig().then(function () {
openHomeTab();
});
$scope.$user = {};
$scope.$year = moment().year();
$scope.openHomeTab = openHomeTab;
$scope.$unreadMailCount = function () {
return MessageService.unreadCount();
};
$scope.showMailBox = function() {
NavService.openTabByName('mail.inbox');
$scope.$timeout(function () {
$scope.$broadcast("on:nav-click", NavService.getSelected());
});
};
var loginAttempts = 0;
var loginWindow = null;
var errorWindow = null;
function showLogin(hide) {
if (loginWindow === null) {
loginWindow = $('#loginWindow')
.attr('title', _t('Log in'))
.dialog({
dialogClass: 'no-close ui-dialog-responsive ui-dialog-small',
autoOpen: false,
modal: true,
position: "center",
width: "auto",
resizable: false,
closeOnEscape: false,
zIndex: 100001,
show: {
effect: 'fade',
duration: 300
},
buttons: [{
text: _t("Log in"),
'class': 'btn btn-primary',
click: function(){
$scope.doLogin();
}
}]
});
$('#loginWindow input').keypress(function(event){
if (event.keyCode === 13)
$scope.doLogin();
});
}
return loginWindow.dialog(hide ? 'close' : 'open').height('auto');
}
function showError(hide) {
if (errorWindow === null) {
errorWindow = $('#errorWindow')
.attr('title', _t('Error'))
.dialog({
dialogClass: 'ui-dialog-error ui-dialog-responsive',
draggable: true,
resizable: false,
closeOnEscape: true,
modal: true,
zIndex: 1100,
width: 420,
open: function(e, ui) {
setTimeout(function () {
if (errorWindow.dialog('isOpen')) {
errorWindow.dialog('moveToTop', true);
}
}, 300);
},
close: function() {
$scope.httpError = {};
$scope.$applyAsync();
},
show: {
effect: 'fade',
duration: 300
},
buttons: [{
text: _t("Show Details"),
'class': 'btn',
click: function(){
var elem = $(this);
$scope.onErrorWindowShow('stacktrace');
$scope.$applyAsync(function () {
setTimeout(function () {
var maxHeight = $(document).height() - 132;
var height = maxHeight;
if (height > elem[0].scrollHeight) {
height = elem[0].scrollHeight + 8;
}
elem.height(height);
elem.dialog('option', 'position', 'center');
elem.dialog('widget').height(elem.dialog('widget').height());
}, 100);
});
}
}, {
text: _t("Close"),
'class': 'btn btn-primary',
click: function() {
errorWindow.dialog('close');
}
}]
});
}
return errorWindow
.dialog(hide ? 'close' : 'open')
.dialog('widget').css('top', 6)
.height('auto');
}
function showNotification(options) {
axelor.notify.error('<p>' + options.message.replace('\n', '<br>') + '</p>', {
title: options.title || options.type || _t('Error')
});
}
$scope.doLogin = function() {
var data = {
username: $('#loginWindow form input:first').val(),
password: $('#loginWindow form input:last').val()
};
var last = axelor.config["user.login"];
$http.post('callback', data).then(function(response){
authService.loginConfirmed();
$('#loginWindow form input').val('');
$('#loginWindow .alert').hide();
if (last !== data.username) {
window.location.reload();
}
});
};
$scope.$on('event:auth-loginRequired', function(event, status) {
$('#loginWindow .alert').hide();
showLogin();
if (loginAttempts++ > 0)
$('#loginWindow .alert.login-failed').show();
if (status === 0 || status === 502)
$('#loginWindow .alert.login-offline').show();
setTimeout(function(){
$('#loginWindow input:first').focus();
}, 300);
});
$scope.$on('event:auth-loginConfirmed', function() {
showLogin(true);
loginAttempts = 0;
fetchConfig();
});
$scope.httpError = {};
$scope.$on('event:http-error', function(event, data) {
var message = _t("Internal Server Error"),
report = data.data || data, stacktrace = null, cause = null, exception;
// unauthorized errors are handled separately
if (data.status === 401) {
return;
}
if (report.popup && report.message) {
return axelor.dialogs.box(report.message, {
title: report.title
});
} else if (report.stacktrace) {
message = report.message || report.string;
exception = report['class'] || '';
if (exception.match(/(OptimisticLockException|StaleObjectStateException)/)) {
message = "<b>" + _t('Concurrent updates error') + '</b><br>' + message;
}
stacktrace = report.stacktrace;
cause = report.cause;
} else if (report.message) {
return showNotification(report);
} else if (_.isString(report)) {
stacktrace = report.replace(/.*<body>|<\/body>.*/g, '');
} else {
return; // no error report, so ignore
}
_.extend($scope.httpError, {
message: message,
stacktrace: stacktrace,
cause: cause
});
showError();
});
$scope.onErrorWindowShow = function(what) {
$scope.httpError.show = what;
};
$scope.$on('$routeChangeSuccess', function(event, current, prev) {
var route = current.$$route,
path = route && route.action ? route.action.split('.') : null;
if (path) {
$scope.routePath = path;
}
});
$scope.routePath = ["main"];
$route.reload();
}
//trigger adjustSize event on window resize -->
$(function(){
$(window).resize(function(event){
if (!event.isTrigger) {
$(document).trigger('adjust:size');
}
$('body').toggleClass('device-small', axelor.device.small);
$('body').toggleClass('device-mobile', axelor.device.mobile);
});
});
})();

103
sophal/js/axelor.auth.js Normal file
View File

@ -0,0 +1,103 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @license HTTP Auth Interceptor Module for AngularJS
* (c) 2012 Witold Szczerba
* License: MIT
*/
(function () {
"use strict";
angular.module('axelor.auth', []).provider('authService', function() {
/**
* Holds all the requests which failed due to 401 response,
* so they can be re-requested in future, once login is completed.
*/
var buffer = [];
/**
* Required by HTTP interceptor.
* Function is attached to provider to be invisible for regular users of this service.
*/
this.pushToBuffer = function(config, deferred) {
buffer.push({
config: config,
deferred: deferred
});
};
this.$get = ['$rootScope','$injector', function($rootScope, $injector) {
var $http; //initialized later because of circular dependency problem
function retry(config, deferred) {
setTimeout(axelor.blockUI);
$http = $http || $injector.get('$http');
$http(config).then(function(response) {
deferred.resolve(response);
});
}
function retryAll() {
for (var i = 0; i < buffer.length; ++i) {
retry(buffer[i].config, buffer[i].deferred);
}
buffer = [];
}
return {
loginConfirmed: function() {
$rootScope.$broadcast('event:auth-loginConfirmed');
retryAll();
}
};
}];
})
/**
* $http interceptor.
* On 401 response - it stores the request and broadcasts 'event:angular-auth-loginRequired'.
*/
.config(['$httpProvider', 'authServiceProvider', function($httpProvider, authServiceProvider) {
$httpProvider.interceptors.push(['$rootScope', '$q', function($rootScope, $q) {
return {
responseError: function error(response) {
if (response.status === 401 && axelor.config['auth.central.client']) {
// redirect to central login page
window.location.href = './?client_name=' + axelor.config['auth.central.client']
+ "&hash_location=" + encodeURIComponent(window.location.hash);
return $q.reject(response);
}
if (response.status === 401 || response.status === 502 || (response.status === 0 && !response.data)) {
var deferred = $q.defer();
authServiceProvider.pushToBuffer(response.config, deferred);
if (!response.config.silent) {
$rootScope.$broadcast('event:auth-loginRequired', response.status);
}
return deferred.promise;
}
// redirect to the CAS login page
if (response.status === 302 || response.status === 307) {
window.location.reload();
}
// otherwise
return $q.reject(response);
}
};
}]);
}]);
})();

1067
sophal/js/axelor.data.js Normal file

File diff suppressed because it is too large Load Diff

851
sophal/js/axelor.ds.js Normal file
View File

@ -0,0 +1,851 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ds = angular.module('axelor.ds', ['ngResource']);
var forEach = angular.forEach,
extend = angular.extend,
isArray = angular.isArray;
ds.factory('MenuService', ['$http', function($http) {
function get(parent) {
return $http.get('ws/action/menu', {
cache: true,
params : {
parent : parent
}
});
}
function all() {
return $http.get('ws/action/menu/all', {
cache: true
});
}
function tags() {
return $http.get('ws/action/menu/tags', {
silent: true,
transformRequest: []
});
}
function action(name, options) {
return $http.post('ws/action/' + name, {
model : 'com.axelor.meta.db.MetaAction',
data : options
});
}
return {
get: get,
all: all,
tags: tags,
action: action
};
}]);
ds.factory('TagService', ['$q', '$timeout', '$rootScope', 'MenuService', function($q, $timeout, $rootScope, MenuService) {
var POLL_INTERVAL = 10000;
var pollResult = {};
var pollPromise = null;
var pollIdle = null;
var listeners = [];
function cancelPolling() {
if (pollPromise) {
$timeout.cancel(pollPromise);
pollPromise = null;
}
if (pollIdle) {
clearTimeout(pollIdle);
pollIdle = null;
}
}
function startPolling() {
if (pollPromise === null) {
findTags();
}
}
var starting = false;
function findTags() {
if (starting) { return; }
if (pollPromise) {
$timeout.cancel(pollPromise);
}
starting = true;
MenuService.tags().success(function (res) {
var data = _.first(res.data);
var values = data.values;
for (var i = 0; i < listeners.length; i++) {
listeners[i](values);
}
pollPromise = $timeout(findTags, POLL_INTERVAL);
if (pollIdle === null) {
pollIdle = setTimeout(cancelPolling, POLL_INTERVAL * 2);
}
starting = false;
});
}
window.addEventListener("mousemove", startPolling, false);
window.addEventListener("mousedown", startPolling, false);
window.addEventListener("keypress", startPolling, false);
window.addEventListener("DOMMouseScroll", startPolling, false);
window.addEventListener("mousewheel", startPolling, false);
window.addEventListener("touchmove", startPolling, false);
window.addEventListener("MSPointerMove", startPolling, false);
// start polling
startPolling();
return {
find: findTags,
listen: function(listener) {
listeners.push(listener);
return function () {
var i = listeners.indexOf(listener);
if (i >= 0) {
listeners.splice(i, 1);
}
return listener;
};
}
};
}]);
ds.factory('ViewService', ['$http', '$q', '$cacheFactory', '$compile', function($http, $q, $cacheFactory, $compile) {
var ViewService = function() {
};
ViewService.prototype.accept = function(params) {
var views = {};
forEach(params.views, function(view){
var type = view.type || view.viewType;
params.viewType = params.viewType || type;
views[type] = extend({}, view, {
deferred: $q.defer()
});
});
return views;
};
ViewService.prototype.compile = function(template) {
return $compile(template);
};
ViewService.prototype.process = function(meta, view, parent) {
var fields = {};
meta = meta || {};
view = view || {};
if (meta.jsonAttrs && view && view.items) {
if (view.type === 'grid') {
view.items = (function (items) {
var button = _.findWhere(items, { type: 'button' });
var index = items.indexOf(button);
if (index < 0) {
index = items.length;
}
items.splice(index, 0, {
type: 'field',
name: 'attrs',
jsonFields: meta.jsonAttrs
});
return items;
})(view.items);
}
if (view.type === 'form') {
view.items.push({
type: 'panel',
title: _t('Attributes'),
itemSpan: 12,
items: [{
type: 'field',
name: 'attrs',
jsonFields: meta.jsonAttrs
}]
});
}
}
view = processJsonForm(view);
meta.fields = processFields(meta.fields);
(function () {
var helps = meta.helps = meta.helps || {};
var items = [];
if (view.helpOverride && view.helpOverride.length) {
helps = _.groupBy(view.helpOverride || [], 'type');
helps = meta.helps = _.object(_.map(helps, function(items, key) {
return [key, _.reduce(items, function(memo, item) {
memo[item.field] = item;
return memo;
}, {})];
}));
if (helps.tooltip && helps.tooltip.__top__) {
view.help = helps.tooltip.__top__.help;
}
}
var help = helps.tooltip || {};
var placeholder = helps.placeholder || {};
var inline = helps.inline || {};
forEach(view.items, function (item) {
if (help[item.name]) {
item.help = help[item.name].help;
}
if (meta.view && meta.view.type === 'form') {
if (placeholder[item.name]) {
item.placeholder = placeholder[item.name].help;
}
if (inline[item.name] && !inline[item.name].used) {
inline[item.name].used = true;
items.push({
type: 'help',
text: inline[item.name].help,
css: inline[item.name].style,
colSpan: 12
});
}
}
items.push(item);
});
forEach(view.toolbar, function (item) {
if (help[item.name]) {
item.help = help[item.name].help;
}
});
if (items.length) {
view.items = items;
}
})();
forEach(view.items || view.pages, function(item) {
processWidget(item);
processSelection(item);
forEach(fields[item.name], function(value, key){
if (!item.hasOwnProperty(key)) {
item[key] = value;
}
});
["canNew", "canView", "canEdit", "canRemove", "canSelect"].forEach(function (name) {
if (item[name] === "false" || item[name] === "true") {
item[name] = item[name] === "true";
}
});
if (item.items || item.pages) {
ViewService.prototype.process(meta, item, view);
}
if (item.password) {
item.widget = "password";
}
if (item.jsonFields && item.widget !== 'json-raw') {
var editor = {
layout: view.type === 'panel-json' ? 'table' : undefined,
flexbox: true,
items: [],
};
var panel = null;
var panelTab = null;
item.jsonFields.sort(function (x, y) { return x.sequence - y.sequence; });
item.jsonFields.forEach(function (field) {
if (field.widgetAttrs) {
field.widgetAttrs = angular.fromJson(field.widgetAttrs);
processWidget(field);
if (field.widgetAttrs.showTitle !== undefined) {
field.showTitle = field.widgetAttrs.showTitle;
}
if (field.widgetAttrs.multiline) {
field.type = 'text';
}
if (field.widgetAttrs.targetName) {
field.targetName = field.widgetAttrs.targetName;
}
}
if (field.type === 'panel' || field.type === 'separator') {
field.visibleInGrid = false;
}
if (field.type === 'panel') {
panel = _.extend({}, field, { items: [] });
if ((field.widgetAttrs || {}).sidebar && parent) {
panel.sidebar = true;
parent.width = 'large';
}
if ((field.widgetAttrs || {}).tab) {
panelTab = panelTab || {
type: 'panel-tabs',
colSpan: 12,
items: []
};
panelTab.items.push(panel);
} else {
editor.items.push(panel);
}
return;
}
if (field.type !== 'separator') {
field.title = field.title || field.autoTitle;
}
var colSpan = (field.widgetAttrs||{}).colSpan || field.colSpan;
if (field.type == 'one-to-many') {
field.type = 'many-to-many';
field.canSelect = false;
}
if (field.type == 'separator' || (field.type == 'many-to-many' && !field.widget)) {
field.showTitle = false;
field.colSpan = colSpan || 12;
}
if (panel) {
panel.items.push(field);
} else {
editor.items.push(field);
}
});
if (panelTab) {
editor.items.push(panelTab);
}
item.widget = 'json-field';
item.editor = editor;
if (!item.viewer) {
item.editor.viewer = true;
}
}
});
// include json fields in grid
if (view.type === 'grid') {
var items = [];
_.each(view.items, function (item) {
if (item.jsonFields) {
_.each(item.jsonFields, function (field) {
var type = field.type || 'text';
if (type.indexOf('-to-many') === -1 && field.visibleInGrid) {
items.push(_.extend({}, field, { name: item.name + '.' + field.name }));
}
});
} else {
items.push(item);
}
});
items = items.sort(function (x, y) { return x.columnSequence - y.columnSequence; });
view.items = items;
}
};
function processJsonForm(view) {
if (view.type !== 'form') return view;
if (view.model !== 'com.axelor.meta.db.MetaJsonRecord') return view;
var panel = _.first(view.items) || {};
var jsonField = _.first(panel.items) || {};
var jsonFields = jsonField.jsonFields || [];
var first = _.first(jsonFields) || {};
if (first.type === 'panel') {
panel.type = 'panel-json';
if (first.widgetAttrs) {
var attrs = angular.fromJson(first.widgetAttrs);
view.width = view.width || attrs.width;
}
}
return view;
}
function processFields(fields) {
var result = {};
if (isArray(fields)) {
forEach(fields, function(field){
field.type = _.chain(field.type || 'string').underscored().dasherize().value();
field.title = field.title || field.autoTitle;
result[field.name] = field;
// if nested field then make it readonly
if (field.name.indexOf('.') > -1) {
field.readonly = true;
field.required = false;
}
processSelection(field);
});
} else {
result = fields || {};
}
return result;
}
function processSelection(field) {
_.each(field.selectionList, function (item) {
if (_.isString(item.data)) {
item.data = angular.fromJson(item.data);
}
});
}
function processWidget(field) {
var attrs = {};
_.each(field.widgetAttrs || {}, function (value, name) {
if (value === "true") value = true;
if (value === "false") value = false;
if (value === "null") value = null;
if (/^(-)?\d+$/.test(value)) value = +(value);
attrs[_.str.camelize(name)] = value;
});
if (field.serverType) {
field.serverType = _.chain(field.serverType).underscored().dasherize().value();
}
field.widgetAttrs = attrs;
}
function useIncluded(view) {
function useMenubar(menubar) {
if (!menubar) return;
var my = view.menubar || menubar;
if (my !== menubar && menubar) {
my = my.concat(menubar);
}
view.menubar = my;
}
function useToolbar(toolbar) {
if (!toolbar) return;
var my = view.toolbar || toolbar;
if (my !== toolbar) {
my = my.concat(toolbar);
}
view.toolbar = my;
}
function useItems(view) {
return useIncluded(view);
}
var items = [];
_.each(view.items, function(item) {
if (item.type === "include") {
if (item.view) {
items = items.concat(useItems(item.view));
useMenubar(item.view.menubar);
useToolbar(item.view.toolbar);
}
} else {
items.push(item);
}
});
return items;
}
function findFields(view, res) {
var result = res || {
fields: [],
related: {}
};
var items = result.fields;
var fields = view.items || view.pages;
if (!fields) return items;
if (view.items && !view._included) {
view._included = true;
fields = view.items = useIncluded(view);
}
function acceptEditor(item) {
var collect = items;
var editor = item.editor;
if (item.target) {
collect = result.related[item.name] || (result.related[item.name] = []);
}
if (editor.fields) {
editor.fields = processFields(editor.fields);
}
var acceptItems = function (items) {
_.each(items, function (child) {
if (child.name && collect.indexOf(child.name) === -1 && child.type === 'field') {
collect.push(child.name);
} else if (child.type === 'panel') {
acceptItems(child.items);
}
if (/RefSelect|ref-select/.test(child.widget)) {
collect.push(child.related);
}
if (child.depends) {
child.depends.split(/\s*,\s*/).forEach(function (name) {
collect.push(name);
});
}
});
};
acceptItems(editor.items);
}
function acceptViewer(item) {
var collect = items;
var viewer = item.viewer;
if (item.target) {
collect = result.related[item.name] || (result.related[item.name] = []);
}
_.each(viewer.fields, function (item) {
collect.push(item.name);
});
if (viewer.fields) {
viewer.fields = processFields(viewer.fields);
}
}
_.each(fields, function(item) {
if (item.editor) acceptEditor(item);
if (item.viewer) acceptViewer(item);
if (item.type === 'panel-related') {
items.push(item.name);
} else if (item.items || item.pages) {
findFields(item, result);
} else if (item.type === 'field') {
items.push(item.name);
}
});
if (view.type === "calendar") {
items.push(view.eventStart);
items.push(view.eventStop);
items.push(view.colorBy);
}
if (view.type === "kanban") {
items.push(view.columnBy);
items.push(view.sequenceBy);
}
if (view.type === "gantt") {
items.push(view.taskUser);
}
return result;
}
var viewCache = $cacheFactory("viewCache", { capacity: 1000 });
function createStore(prefix) {
var toKey = function (name) {
return prefix + ':' + axelor.config['user.id'] + ':' + name;
};
return {
get: function (name) {
return new $q(function (resolve) {
resolve(angular.copy(viewCache.get(toKey(name))));
});
},
set: function (name, value) {
if (value) {
return new $q(function (resolve) {
var val = viewCache.put(toKey(name), angular.copy(value));
resolve(val);
});
}
return $q.resolve(value);
}
};
}
var PENDING_REQUESTS = {};
var FIELDS = createStore('f');
var VIEWS = createStore('v');
var PERMS = createStore('p');
ViewService.prototype.getMetaDef = function(model, view, context) {
var self = this;
var deferred = $q.defer();
var promise = deferred.promise;
promise.success = function(fn) {
promise.then(function(res) {
fn(res.fields, res.view);
});
return promise;
};
function process(data) {
data.view.perms = data.view.perms || data.perms;
self.process(data, data.view);
if (data.perms && data.perms.write === false) {
data.view.editable = false;
}
return data;
}
function updateFields(fetched) {
return FIELDS.get(model).then(function (current) {
current = current || {};
if (current !== fetched.fields) {
_.extend(current, _.object(_.pluck(fetched.fields, 'name'), fetched.fields));
}
return $q.all([FIELDS.set(model, current), PERMS.set(model, fetched.perms)]);
});
}
function fetchFields(data) {
var fields_data = findFields(data.view);
var fields = _.unique(_.compact(fields_data.fields.sort()));
data.related = fields_data.related;
if (_.isArray(data.fields) && data.fields.length > 0) {
updateFields(data).then(function () {
deferred.resolve(process(data));
});
return promise;
}
if (!model || _.isEmpty(fields)) {
deferred.resolve(data);
return promise;
}
function resolve(fetched) {
return updateFields(fetched).then(function (res) {
var current = res[0];
var perms = res[1];
var result = _.extend({}, fetched, {
fields: _.map(fields, function(n) { return current[n]; }),
perms: perms
});
result.view = data.view || view;
result.fields = _.compact(result.fields);
result.related = data.related;
result = process(result);
deferred.resolve(result);
return promise;
});
}
$q.all([FIELDS.get(model), PERMS.get(model)]).then(function (res) {
var fetchedFields = res[0] || {};
var pendingFields = _.filter(fields, function (n) { return !fetchedFields.hasOwnProperty(n); });
if (pendingFields.length == 0) {
resolve({
fields: _.values(fetchedFields),
perms: res[1]
});
return promise;
}
var key = _.flatten([model, pendingFields]).join();
var pending = PENDING_REQUESTS[key];
function clear() {
delete PENDING_REQUESTS[key];
}
if (pending) {
pending.then(clear, clear);
pending.then(function (response) {
resolve((response.data || {}).data);
});
return promise;
}
pending = $http.post('ws/meta/view/fields', { model: model, fields: pendingFields }).then(function (response) {
resolve((response.data || {}).data);
});
pending.then(clear, clear);
PENDING_REQUESTS[key] = pending;
});
return promise;
}
function fetchView() {
var key = [model, view.type, view.name].join(':');
function resolve(response) {
var result = (response.data || {}).data;
if (!result || !result.view) {
return deferred.reject('view not found', view);
}
if (result.searchForm) {
result.view.searchForm = result.searchForm;
}
if (_.isArray(result.view.items)) {
var fieldsPromise = fetchFields(result, key);
if (!_.isArray(view.items)) {
// only cache fetched views
return fieldsPromise.then(function (res) {
return VIEWS.set(key, _.extend({}, res, { view: result.view }));
});
}
return fieldsPromise;
}
result = {
fields: result.view.items,
view: result.view
};
VIEWS.set(key, result).then(function () {
deferred.resolve(result);
});
}
VIEWS.get(key).then(function (loaded) {
if (loaded) {
return deferred.resolve(loaded);
}
function clear() {
delete PENDING_REQUESTS[key];
}
var pending = PENDING_REQUESTS[key];
if (pending) {
pending.then(clear, clear);
return pending.then(resolve);
}
pending = $http.post('ws/meta/view', {
model: model,
data: {
type: view.type,
name: view.name,
context: context
}
});
pending.then(resolve);
pending.then(clear, clear);
PENDING_REQUESTS[key] = pending;
});
return promise;
}
if (_.isArray(view.items)) {
return fetchFields({ view: view, fields: view.fields });
}
return fetchView();
};
ViewService.prototype.defer = function() {
return $q.defer();
};
ViewService.prototype.action = function(action, model, context, data) {
var params = {
model: model,
action: action,
data: data || {
context: _.extend({ _model: model }, context)
}
};
var promise = $http.post('ws/action', params);
promise.success = function(fn) {
promise.then(function(response){
fn(response.data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
};
ViewService.prototype.getFields = function(model, jsonModel) {
var that = this,
promise = $http.get('ws/meta/fields/' + model, {
cache: true,
params: jsonModel ? {
jsonModel: jsonModel
} : undefined
});
promise.success = function(fn) {
promise.then(function(response) {
var res = response.data,
data = res.data;
that.process(data);
fn(data.fields, data.jsonFields);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
};
ViewService.prototype.save = function(schema) {
var promise = $http.post("ws/meta/view/save", {
data: schema
});
promise.success = function(fn) {
promise.then(function(response) {
var res = response.data,
data = res.data;
fn(data);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, fn);
return promise;
};
return promise;
};
return new ViewService();
}]);
})();

609
sophal/js/axelor.nav.js Normal file
View File

@ -0,0 +1,609 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var app = angular.module("axelor.app");
function useSingleTabOnly() {
return axelor.device.mobile
|| !!axelor.config['view.single.tab']
|| axelor.config['user.singleTab']
|| +(axelor.config["view.tabs.max"]) === 0
|| +(axelor.config["view.tabs.max"]) === 1;
}
app.factory('NavService', ['$location', 'MenuService', function($location, MenuService) {
var tabs = [];
var popups = [];
var selected = null;
var VIEW_TYPES = {
'list' : 'grid',
'edit' : 'form'
};
function findTab(key) {
return _.find(tabs, function(tab){
return tab.action == key;
});
}
function findTabTitle(tab) {
var first;
if (tab.title) {
return tab.title;
}
first = _.first(tab.views);
if (first) {
return first.title || first.name;
}
return tab.name || "Unknown";
}
function openView(view, options) {
if (view && (view.type || view.viewType) == 'html') {
var first = _.first(view.views) || view;
view.views = [{
name: first.name,
resource: first.resource,
title: first.title,
type: 'html'
}];
if ((view.params||{}).target === "_blank") {
var url = first.name || first.resource;
return setTimeout(function () {
window.open(url);
});
}
}
var closable = options && options.__tab_closable;
if (!closable && view.params && view.params.closable !== undefined) {
closable = view.params.closable;
}
view.closable = closable;
openTab(view, options);
}
function openTabByName(name, options) {
var tab = findTab(name);
if (tab) {
return openTab(tab, options);
}
return MenuService.action(name).success(function(result){
if (!result.data) {
return;
}
var view = result.data[0].view;
view.action = name;
return openView(view, options);
});
}
function openTabAsPopup(tab, options) {
popups.push(tab);
}
function openTab(tab, options) {
if (tab && tab.$popupParent) {
return openTabAsPopup(tab, options);
}
options = options || tab.options;
if (options && options.mode) {
tab.viewType = VIEW_TYPES[options.mode] || options.mode;
}
tab.options = options;
tab.title = tab.title || findTabTitle(tab);
if (tab.action && MenuService.updateTabStyle) {
MenuService.updateTabStyle(tab);
}
function __doSelect(found) {
var lastScope = (selected||{}).$viewScope || {};
if (lastScope.$locationChangeOff) {
lastScope.$locationChangeOff();
}
found.selected = true;
selected = found;
if (options && tab.$viewScope) {
var view = tab.$viewScope._views[tab.viewType],
promise = view ? view.deferred.promise : null;
if (promise) {
promise.then(function(viewScope) {
viewScope.setRouteOptions(options);
});
}
}
setTimeout(function(){
axelor.$adjustSize();
});
}
function __singleShow(found) {
tabs.length = 0;
tabs.push(found);
return __doSelect(found);
}
var found = findTab(tab.action);
if (useSingleTabOnly()) {
if (found) {
return __singleShow(found);
}
var last = _.last(tabs);
if (last) {
return closeTab(last, function () {
__singleShow(tab);
});
}
return __singleShow(tab);
}
if (!found) {
found = tab;
__closeUnusedTabs();
if (options && options.__tab_prepend) {
tabs.unshift(tab);
} else {
tabs.push(tab);
}
}
_.each(tabs, function(tab) {
tab.selected = false;
});
return __doSelect(found);
}
var MAX_TABS;
function __closeUnusedTabs() {
if (MAX_TABS === undefined) {
MAX_TABS = +(axelor.config["view.tabs.max"]) || -1;
}
if (MAX_TABS <= 0 || tabs.length < MAX_TABS) {
return;
}
var all = _.filter(tabs, function (tab) {
return !tab.selected && canCloseTab(tab);
});
var doClose = function doClose(tab) {
var index = _.indexOf(tabs, tab);
var vs = tab.$viewScope;
if (vs && vs.isDirty && vs.isDirty()) return;
if (vs && vs.$details && vs.$details.isDirty && vs.$details.isDirty()) return;
tabs.splice(index, 1);
};
for (var i = 0; i < all.length; i++) {
doClose(all[i]);
if (tabs.length === 0) selected = null;
if (tabs.length < MAX_TABS) break;
}
}
function __closeTab(tab, callback) {
var all = tab.$popupParent ? popups : tabs;
var index = _.indexOf(all, tab);
// remove tab
all.splice(index, 1);
if (tabs.length === 0) {
selected = null;
}
if (_.isFunction(callback)) {
callback();
}
if (tab.$popupParent) {
return;
}
if (tab.selected) {
if (index == tabs.length)
index -= 1;
_.each(all, function(tab){
tab.selected = false;
});
var select = all[index];
if (select) {
select.selected = true;
openTab(select);
} else {
$location.path('/');
}
}
}
function canCloseTab(tab) {
return tab.closable === undefined ? true : tab.closable;
}
function closeTab(tab, callback) {
var viewScope = tab.$viewScope;
if (viewScope && viewScope.confirmDirty) {
viewScope.confirmDirty(function(){
__closeTab(tab, callback);
});
} else {
__closeTab(tab, callback);
}
}
function closeTabs(selection) {
var all = _.flatten([selection], true);
function select(tab) {
if (!tab.selected) {
tab.selected = true;
openTab(tab);
}
}
function close(tab, ignore) {
var at = tabs.indexOf(tab);
if (at > -1) {
tabs.splice(at, 1);
}
closeTabs(_.difference(selection, [ignore, tab]));
if (tabs.length === 0) {
selected = null;
}
}
function doConfirm(tab, viewScope) {
return viewScope.confirmDirty(function(){
return close(tab);
}, function() {
close(null, tab);
viewScope.$applyAsync();
});
}
for (var i = 0; i < all.length; i++) {
var tab = all[i];
var viewScope = tab.$viewScope;
if (viewScope && viewScope.confirmDirty) {
select(tab);
return doConfirm(tab, viewScope);
}
return close(tab);
}
if (tabs.indexOf(selected) == -1) {
selected = null;
}
if (selected) {
return openTab(selected);
}
var first = _.first(tabs);
if (first && !first.selected) {
return openTab(first);
}
axelor.$adjustSize();
}
function closeTabOthers(current) {
var rest = _.filter(tabs, function(tab) {
return canCloseTab(tab) && tab !== current;
});
if (current && !current.selected) {
current.selected = true;
openTab(current);
}
return closeTabs(rest);
}
function closeTabAll() {
closeTabOthers();
}
function reloadTab(current) {
var viewScope = current.$viewScope;
if (viewScope) {
viewScope.$broadcast('on:tab-reload', current);
}
}
function getTabs() {
return tabs;
}
function getPopups() {
return popups;
}
function getSelected() {
return selected;
}
return {
openTabByName: openTabByName,
openTab: openTab,
openView: openView,
canCloseTab: canCloseTab,
reloadTab: reloadTab,
closeTab: closeTab,
closeTabOthers: closeTabOthers,
closeTabAll: closeTabAll,
getTabs: getTabs,
getPopups: getPopups,
getSelected: getSelected
};
}]);
NavCtrl.$inject = ['$scope', '$rootScope', '$location', 'NavService'];
function NavCtrl($scope, $rootScope, $location, NavService) {
$scope.singleTabOnly = useSingleTabOnly();
Object.defineProperty($scope, '$location', {
get: function() {
return $location;
}
});
Object.defineProperty($scope, 'navTabs', {
get: function() {
return NavService.getTabs();
}
});
Object.defineProperty($scope, 'navPopups', {
get: function() {
return NavService.getPopups();
}
});
Object.defineProperty($scope, 'selectedTab', {
get: function() {
return NavService.getSelected();
}
});
$scope.hasNabPopups = function () {
return $scope.navPopups && $scope.navPopups.length > 0;
};
$scope.menuClick = function(event, record) {
if (!record.action) {
return;
}
if (axelor.device.small) {
$("#offcanvas").removeClass("active");
}
$scope.openTabByName(record.action);
$scope.$applyAsync();
};
$scope.navClick = function(tab) {
$scope.openTab(tab);
$scope.$broadcast("on:nav-click", tab);
};
$scope.$on("on:update-route", update);
function update(event) {
var tab = $scope.selectedTab,
scope = event.targetScope;
if (!tab || !tab.action || scope !== tab.$viewScope || !scope.getRouteOptions) {
return;
}
if (tab.action.indexOf('$act') > -1) {
return;
}
var path = tab.action,
opts = scope.getRouteOptions(),
mode = opts.mode,
args = opts.args;
path = "/ds/" + path + "/" + mode;
args = _.filter(args, function(arg) {
return _.isNumber(args) || arg;
});
if (args.length) {
path += "/" + args.join("/");
}
if ($location.$$path !== path) {
$location.path(path);
$location.search(opts.query || "");
}
}
$scope.canCloseTab = function(tab) {
return NavService.canCloseTab(tab);
};
$scope.openTab = function(tab, options) {
return NavService.openTab(tab, options);
};
$scope.openTabByName = function(name, options) {
return NavService.openTabByName(name, options);
};
$scope.closeTab = function(tab, callback) {
var wasSelected = tab.selected;
if (NavService.canCloseTab(tab)) {
NavService.closeTab(tab, callback);
if ($scope.selectedTab && wasSelected) {
$scope.$broadcast("on:nav-click", $scope.selectedTab);
}
}
};
$scope.closeTabOthers = function(tab) {
var wasSelected = tab.selected;
NavService.closeTabOthers(tab);
if ($scope.selectedTab === tab && !wasSelected) {
$scope.$broadcast("on:nav-click", tab);
}
};
$scope.closeTabAll = function() {
return NavService.closeTabAll();
};
$scope.reloadTab = function(tab) {
return NavService.reloadTab(tab);
};
$scope.tabTitle = function(tab) {
var vs = tab.$viewScope || {};
if (vs.viewType === "form") {
return vs.viewTitle || tab.title;
}
return tab.title;
};
$scope.tabDirty = function(tab) {
var viewScope = tab.$viewScope;
if (viewScope && viewScope.isDirty) {
return viewScope.isDirty();
}
return false;
};
// expose common methods to $rootScope
$scope.$root.openTab = $scope.openTab;
$scope.$root.openTabByName = $scope.openTabByName;
$scope.$watch('selectedTab.viewType', function tabViewTypeWatch(viewType){
if (viewType) {
axelor.$adjustSize();
}
});
$scope.$watch('routePath', function routePathWatch(path) {
$scope.openHomeTab();
});
var confirm = _t('Current changes will be lost.');
function onbeforeunload(e) {
var tabs = $scope.navTabs || [];
for (var i = 0; i < tabs.length; i++) {
var vs = (tabs[i]||{}).$viewScope;
if (vs && vs.$$dirty) {
return confirm;
}
}
}
$(function () {
// menu toggle logic
var menuToggled = false;
var navigator = axelor.config["user.navigator"];
if (navigator !== 'hidden') {
$('#offcanvas-toggle').find('a').click(function (e) {
var active = ! $("#offcanvas").hasClass('inactive');
if (active && axelor.device.small) {
active = $("#offcanvas").hasClass('active');
}
$("#offcanvas").toggleClass("active", !active && axelor.device.small);
$("#offcanvas").toggleClass("inactive", active && !axelor.device.small);
if (!axelor.device.mobile) {
setTimeout(axelor.$adjustSize, 100);
}
});
}
$("#offcanvas,#offcanvas-toggle").toggleClass("hidden-menu", navigator === "hidden");
if (navigator === "collapse") {
$("#offcanvas").addClass("inactive");
}
$scope.ajaxStop(function () {
setTimeout(function () {
$("#offcanvas,#offcanvas-toggle").removeClass("hidden");
}, 100);
}, 100);
$(window).on('resize', _.debounce(function () {
$("#offcanvas").removeClass(axelor.device.small ? 'inactive' : 'active');
setTimeout(axelor.$adjustSize, 100);
}, 100));
// confirm dirty
$(window).on('beforeunload', onbeforeunload);
});
}
TabCtrl.$inject = ['$scope', '$location', '$routeParams'];
function TabCtrl($scope, $location, $routeParams) {
var homeAction = axelor.config["user.action"],
params = _.clone($routeParams),
search = _.clone($location.$$search);
var opts = {
mode: params.mode,
state: params.state,
search: search
};
if (homeAction === params.resource) {
_.extend(opts, {
__tab_prepend: true,
__tab_closable: false
});
}
if (params.resource) {
$scope.openTabByName(params.resource, opts);
}
}
app.controller("NavCtrl", NavCtrl);
app.controller("TabCtrl", TabCtrl);
})();

201
sophal/js/axelor.ng.js Normal file
View File

@ -0,0 +1,201 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var module = angular.module('axelor.ng', []);
module.config(['$provide', function($provide) {
$provide.decorator('$rootScope', ['$delegate', '$exceptionHandler', '$injector', function ($rootScope, $exceptionHandler, $injector) {
var __orig__ = Object.getPrototypeOf($rootScope),
__super__ = {},
__custom__ = {};
for (var name in __orig__) {
if (angular.isFunction(__orig__[name])) {
__super__[name] = __orig__[name];
}
}
var $q = null,
$http = null,
$timeout = null;
__custom__.ajaxStop = function ajaxStop(callback, context) {
var count, wait;
if ($http === null) {
$http = $injector.get('$http');
}
count = _.size($http.pendingRequests || []);
wait = _.last(arguments) || 10;
if (_.isNumber(context)) {
context = undefined;
}
if (count > 0) {
return _.delay(ajaxStop.bind(this), wait, callback, context);
}
if (_.isFunction(callback)) {
return this.$timeout(callback.bind(context), wait);
}
};
// expose _t() to use in template
__custom__._t = _t;
__custom__.$actionPromises = [];
__custom__.waitForActions = function waitForActions(callback, wait) {
if ($q === null) {
$q = $injector.get('$q');
}
var that = this;
var args = arguments;
var waitFor = wait || 10;
this.$timeout(function () {
// wait for any pending ajax requests
that.ajaxStop(function () {
var all = args.length === 3 ? args[2] : that.$actionPromises;
// wait for actions
$q.all(all).then(function () {
// if new actions are executed, wait for them
if (args.length !== 3 && that.$actionPromises.length) {
return _.delay(waitForActions.bind(that), 10, callback);
}
if (callback) {
callback();
}
}, callback);
});
}, waitFor);
};
__custom__.$callWhen = function (predicate, callback, wait) {
var count = wait || 100;
function later() {
if (count-- === 0 || (_.isFunction(predicate) && predicate())) {
return callback();
}
return _.delay(later, count);
}
this.$timeout(later, wait);
};
__custom__.$timeout = function(func, wait, invokeApply) {
if ($timeout === null) {
$timeout = $injector.get('$timeout');
}
if (arguments.length === 0) {
return $timeout();
}
return $timeout.apply(null, arguments);
};
__custom__.$onAdjust = function (events, handler, wait) {
var names = events;
if (_.isFunction(names)) {
wait = handler;
handler = names;
names = 'adjust:size';
} else {
names = names.replace(/(\w+)/g, 'adjust:$1');
}
var func = wait ? _.debounce(handler, wait) : handler;
$(document).on(names, func);
this.$on('$destroy', function () {
$(document).off(names, func);
});
};
__custom__.$new = function $new() {
var inst = __super__.$new.apply(this, arguments);
inst.$$watchChecker = this.$$watchChecker;
inst.$$watchInitialized = false;
inst.$$childCanWatch = true;
inst.$$shouldWatch = false;
inst.$$popupStack = this.$$popupStack || (this.$$popupStack = []);
return inst;
};
// make sure to patch $rootScope.$digest with
// if ((!current.$$canWatch || current.$$canWatch(current)) && (watchers = current.$$watchers)) {
// ...
// }
__custom__.$$canWatch = function () {
if (!this.$$watchInitialized || !this.$$watchChecker) {
this.$$watchInitialized = true;
return true;
}
if (this.$$shouldWatch === true) {
return true;
}
var parent = this.$parent || {};
if (parent.$$childCanWatch !== undefined && !parent.$$childCanWatch) {
return false;
}
this.$$childCanWatch = this.$$watchChecker(this);
return this.$$childCanWatch;
};
__custom__.$watchChecker = function (checker) {
var self = this,
previous = this.$$watchChecker;
if (this.$$watchChecker === null) {
this.$$watchChecker = checker;
} else {
this.$$watchChecker = function() {
return previous(self) && checker(self);
};
}
};
var __super__$$apply = _.debounce(__super__.$apply, 100);
__custom__.$apply = function $apply() {
return arguments.length === 0
? __super__$$apply.apply(this)
: __super__.$apply.apply(this, arguments);
};
__custom__.$applyNow = function $applyNow() {
return __super__.$apply.apply(this, arguments);
};
angular.extend(__orig__, __custom__);
angular.extend($rootScope, __custom__);
$rootScope.$$watchChecker = null;
return $rootScope;
}]);
}]);
})();

126
sophal/js/axelor.ns.js Normal file
View File

@ -0,0 +1,126 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
// create global axelor namespace if not exists
window.axelor = window.axelor || {};
window.axelor.config = {};
// browser detection (adopted from jquery)
var ua = navigator.userAgent.toLowerCase();
var browser = {};
var match =
/(edge)[\/]([\w.]+)/.exec(ua) ||
/(opr)[\/]([\w.]+)/.exec(ua) ||
/(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(version)(applewebkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+).*(version)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[];
var matched = {
browser: match[5] || match[3] || match[1] || "",
version: match[4] || match[2] || "0"
};
if (matched.browser) {
browser[matched.browser] = true;
browser.version = matched.version;
}
if (browser.chrome || browser.opr || browser.safari) {
browser.webkit = true;
}
// IE11
if (browser.rv) {
var ie = "msie";
matched.browser = ie;
browser[ie] = true;
}
// recent opera
if (browser.opr ) {
var opera = "opera";
matched.browser = opera;
browser[opera] = true;
}
// screen size detection
var device = {
small: false,
large: false
};
device.large = $(window).width() > 768;
device.small = !device.large;
device.mobile = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(ua);
axelor.browser = browser;
axelor.device = device;
function sanitizeElement(element) {
$.each(element.attributes, function() {
var attr = this.name;
var value = this.value;
if (attr.indexOf('xss-on') === 0 || value.indexOf('javascript:') === 0) {
$(element).removeAttr(attr);
}
});
}
// this function removes <script> and event attributes (onerror, onload etc.) from the given html text
function sanitizeText(html) {
if (typeof html !== 'string') {
return html;
}
var value = "<div>" + html.replace(/(\s)(on(?:\w+))(\s*=)/, '$1xss-$2$3') + "</div>";
var elems = $($.parseHTML(value, null, false));
elems.find('*').each(function() {
sanitizeElement(this);
});
return elems.html();
}
function sanitize() {
if (arguments.length === 0) {
return;
}
var args = arguments.length === 1 ? arguments[0] : Array.prototype.slice.call(arguments);
return Array.isArray(args) ? args.map(function (item) {
return Array.isArray(item) ? sanitize(item) : sanitizeText(item);
}) : sanitizeText(args);
}
axelor.sanitize = sanitize;
// sanitize jquery html function
var jq = {
html: $.fn.html
};
$.fn.html = function html() { return jq.html.apply(this, sanitize(arguments)); };
})();

120
sophal/js/axelor.prefs.js Normal file
View File

@ -0,0 +1,120 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module("axelor.ui");
function UserCtrl($scope, $element, $location, DataSource, ViewService) {
$scope._viewParams = {
model: 'com.axelor.auth.db.User',
views: [{name: 'user-preferences-form', type: 'form'}],
recordId: axelor.config['user.id']
};
ui.ViewCtrl($scope, DataSource, ViewService);
ui.FormViewCtrl($scope, $element);
$scope.onClose = function() {
$scope.confirmDirty(doClose);
};
var __version = null;
$scope.$watch('record.version', function recordVersionWatch(value) {
if (value === null || value === undefined) return;
if (__version !== null) return;
__version = value;
});
function doClose() {
if (!$scope.isDirty()) {
var rec = $scope.record || {};
axelor.config["user.action"] = rec.homeAction;
}
window.history.back();
if (__version === ($scope.record || {}).version) {
return;
}
setTimeout(function() {
window.location.reload();
}, 100);
}
$scope.isMidForm = function (elem) {
return $element.find('form.mid-form').size();
};
$scope.setEditable();
$scope.show();
$scope.ajaxStop(function () {
$scope.$applyAsync();
});
}
function AboutCtrl($scope) {
$scope.appName = axelor.config["application.name"];
$scope.appDescription = axelor.config["application.description"];
$scope.appVersion = axelor.config["application.version"];
$scope.appVersionShort = $scope.appVersion.substring(0, $scope.appVersion.lastIndexOf('.'));
$scope.appCopyright = axelor.config["application.copyright"];
$scope.appSdk = axelor.config["application.sdk"];
$scope.appSdkShort = $scope.appSdk.substring(0, $scope.appSdk.lastIndexOf('.'));
$scope.appHome = axelor.config["application.home"];
$scope.appHelp = axelor.config["application.help"];
$scope.appYear = moment().year();
}
function SystemCtrl($scope, $element, $location, $http) {
var promise = null;
$scope.onRefresh = function () {
if (promise) {
return;
}
promise = $http.get("ws/app/sysinfo").then(function (res) {
var info = res.data;
_.each(info.users, function (item) {
item.loginTime = moment(item.loginTime).format('L LT');
item.accessTime = moment(item.accessTime).format('L LT');
});
$scope.info = info;
promise = null;
});
return promise;
};
$scope.onClose = function () {
window.history.back();
};
$scope.onRefresh();
}
ui.controller("UserCtrl", ['$scope', '$element', '$location', 'DataSource', 'ViewService', UserCtrl]);
ui.controller("SystemCtrl", ['$scope', '$element', '$location', '$http', SystemCtrl]);
ui.controller("AboutCtrl", ['$scope', AboutCtrl]);
})();

24
sophal/js/axelor.ui.js Normal file
View File

@ -0,0 +1,24 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
angular.module('axelor.ui', [
'ngRoute',
'ngSanitize',
'ngAnimate',
'ui.sortable',
'axelor.ds'
]);

File diff suppressed because it is too large Load Diff

700
sophal/js/form/form.base.js Normal file
View File

@ -0,0 +1,700 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/* jshint validthis: true */
"use strict";
var ui = angular.module('axelor.ui');
var widgets = {};
var registry = {};
var metaWidgets = [];
/**
* Perform common compile operations.
*
* example:
* ui.formCompile.call(this, element, attrs)
*/
ui.formCompile = function(element, attrs, linkerFn) {
var showTitle = attrs.showTitle || this.showTitle,
title = attrs.title || attrs.field;
attrs.$set('show-title', showTitle, true, 'x-show-title');
if (title) {
attrs.$set('title', title, true, 'x-title');
}
if (this.cellCss) {
attrs.$set('x-cell-css', this.cellCss);
}
function link(scope, element, attrs, controller) {
element.addClass(this.css).parent().addClass(this.cellCss);
element.data('$attrs', attrs); // store the attrs object for event handlers
var getViewDef = this.getViewDef || scope.getViewDef || function() { return {}; };
var field = getViewDef.call(scope, element);
var props = _.extend(_.pick(field, 'readonly,required,hidden,collapse,precision,scale,prompt,title,domain,css,icon,selection-in'.split(',')),
_.pick(field.widgetAttrs || {}, 'precision,scale,domain'.split(',')));
var state = _.clone(props);
function resetAttrs() {
var label = element.data('label');
state = _.clone(props);
state["force-edit"] = false;
if (label && state.title) {
var span = label.children('span[ui-help-popover]:first');
if (span.length === 0) {
span = label;
}
span.html(state.title);
}
}
if (field.css) {
element.addClass(field.css);
}
if (field.width && field.width !== '*' && !element.is('label')) {
element.width(field.width);
}
if (field.translatable) {
element.addClass("translatable");
}
scope.$events = {};
scope.field = field || {};
scope.$$readonly = undefined;
scope.attr = function(name) {
if (arguments.length > 1) {
var old = state[name];
state[name] = arguments[1];
if (name === "highlight") {
setHighlight(state.highlight);
}
if (old !== state[name]) {
scope.$broadcast("on:attrs-changed", {
name: name,
value: state[name]
});
}
}
var res = state[name];
if (res === undefined) {
res = field[name];
}
return res;
};
scope.$on("on:edit", function(e, rec) {
if (angular.equals(rec, {})) {
resetAttrs();
}
scope.$$readonly = scope.$$isReadonly();
});
scope.$on("on:attrs-changed", function(event, attr) {
if (attr.name === "readonly" || attr.name === "force-edit") {
scope.$$readonly = scope.$$isReadonly();
}
if (attr.name === "readonly") {
element.attr("x-readonly", scope.$$readonly);
}
});
scope.$watch("isEditable()", function isEditableWatch(editable, old) {
if (editable === undefined) return;
if (editable === old) return;
scope.$$readonly = scope.$$isReadonly();
});
// js expressions should be evaluated on dummy value changes
if (field.name && field.name[0] === '$') {
scope.$watch('record.' + field.name, function fieldValueWatch(a, b) {
if (a !== b) {
scope.$broadcastRecordChange();
}
});
}
scope.isRequired = function() {
return this.attr("required") && this.text !== 0 && !this.text;
};
scope.isReadonlyExclusive = function() {
var parent = this.$parent || {};
var readonly = this.attr("readonly");
if (scope._isPopup && !parent._isPopup) {
return readonly || false;
}
if (parent.isReadonlyExclusive && parent.isReadonlyExclusive()) {
return true;
}
if (readonly !== undefined) {
return readonly || false;
}
return readonly || false;
};
scope.isReadonly = function() {
if (scope.$$readonly === undefined) {
scope.$$readonly = scope.$$isReadonly();
}
return scope.$$readonly;
};
scope.$$isReadonly = function() {
if ((this.hasPermission && !this.hasPermission('read')) || this.isReadonlyExclusive()) {
return true;
}
if (!this.attr("readonly") && this.attr("force-edit")) {
return false;
}
if (scope.isEditable && !scope.isEditable()) {
return true;
}
return this.attr("readonly") || false;
};
scope.isHidden = function() {
return this.attr("hidden") || (this.$parent && this.$parent.isHidden && this.$parent.isHidden()) || false;
};
scope.fireAction = function(name, success, error) {
var handler = this.$events[name];
if (handler) {
return handler().then(success, error);
}
};
if (angular.isFunction(this._link_internal)) {
this._link_internal.call(this, scope, element, attrs, controller);
}
if (angular.isFunction(this.init)) {
this.init.call(this, scope);
}
if (angular.isFunction(this.link)) {
this.link.call(this, scope, element, attrs, controller);
}
function hideWidget(hidden) {
var elem = element,
parent = elem.parent('td,.form-item'),
label = elem.data('label') || $(),
label_parent = label.parent('td,.form-item'),
isTable = parent.is('td');
// label scope should use same isHidden method (#1514)
var lScope = label.data('$scope');
if (lScope && lScope.isHidden !== scope.isHidden) {
lScope.isHidden = scope.isHidden;
}
elem = isTable && parent.length ? parent : elem;
label = isTable && label_parent.length ? label_parent : label;
if (!isTable) {
parent.toggleClass("form-item-hidden", hidden);
}
if (hidden) {
elem.add(label).hide();
} else {
elem.add(label).show().css('display', ''); //XXX: jquery may add display style
}
return axelor.$adjustSize();
}
var hideFn = _.contains(this.handles, 'isHidden') ? angular.noop : hideWidget;
var hiddenSet = false;
scope.$watch("isHidden()", function isHiddenWatch(hidden, old) {
if (hiddenSet && hidden === old) return;
hiddenSet = true;
return hideFn(hidden);
});
var readonlySet = false;
scope.$watch("isReadonly()", function isReadonlyWatch(readonly, old) {
if (readonlySet && readonly === old) return;
readonlySet = true;
element.toggleClass("readonly", readonly);
element.toggleClass("editable", !readonly);
if (scope.canEdit) {
element.toggleClass("no-edit", scope.canEdit() === false);
}
});
function setHighlight(args) {
function doHilite(params, passed) {
var label = element.data('label') || $();
element.toggleClass(params.css, passed);
label.toggleClass(params.css.replace(/(hilite-[^-]+\b(?!-))/g, ''), passed);
}
_.each(field.hilites, function(p) {
if (p.css) doHilite(p, false);
});
if (args && args.hilite && args.hilite.css) {
doHilite(args.hilite, args.passed);
}
}
this.prepare(scope, element, attrs, controller);
scope.$evalAsync(function() {
if (scope.isHidden()) {
hideFn(true);
}
});
}
return angular.bind(this, link);
};
ui.formDirective = function(name, object) {
if (object.compile === undefined) {
object.compile = angular.bind(object, function(element, attrs){
return ui.formCompile.apply(this, arguments);
});
}
if (object.restrict === undefined) {
object.restrict = 'EA';
}
if (object.template && !object.replace) {
object.replace = true;
}
if (object.cellCss === undefined) {
object.cellCss = 'form-item';
}
if (object.scope === undefined) {
object.scope = true;
}
if (object.require === undefined) {
object.require = '?ngModel';
}
function prepare_templates($compile) {
object.prepare = angular.bind(object, function(scope, element, attrs, model) {
var self = this;
if (!this.template_editable && !this.template_readonly) {
return;
}
scope.$elem_editable = null;
scope.$elem_readonly = null;
function showEditable() {
var template_editable = self.template_editable;
if (scope.field && scope.field.editor) {
template_editable = $('<div ui-panel-editor>');
}
if (_.isFunction(self.template_editable)) {
template_editable = self.template_editable(scope);
}
if (!template_editable) {
return false;
}
if (!scope.$elem_editable) {
scope.$elem_editable = $compile(template_editable)(scope);
if (self.link_editable) {
self.link_editable.call(self, scope, scope.$elem_editable, attrs, model);
}
if (scope.validate) {
model.$validators.valid = function(modelValue, viewValue) {
return !!scope.validate(viewValue);
};
}
// focus the first input field
if (scope.$elem_editable.is('.input-append,.picker-input')) {
scope.$elem_editable.on('click', '.btn, i', function(){
if (!axelor.device.mobile) {
scope.$elem_editable.find('input:first').focus();
}
});
}
if (scope.$elem_editable.is(':input')) {
scope.$elem_editable.attr('placeholder', scope.field.placeholder);
}
if (scope.$elem_editable.is('.picker-input:not(.tag-select)')) {
scope.$elem_editable.find(':input:first').attr('placeholder', scope.field.placeholder);
}
}
if (scope.$elem_readonly) {
scope.$elem_readonly.detach();
}
element.append(scope.$elem_editable);
if (scope.$render_editable) scope.$render_editable();
return true;
}
function showReadonly() {
var field = scope.field || {};
var template_readonly = self.template_readonly;
if (field.viewer) {
template_readonly = field.viewer.template;
scope.$moment = function(d) { return moment(d); };
scope.$number = function(d) { return +d; };
scope.$image = function (fieldName, imageName) { return ui.formatters.$image(this, fieldName, imageName); };
scope.$fmt = function (fieldName, fieldValue) {
var args = [this, fieldName];
if (arguments.length > 1) {
args.push(fieldValue);
}
return ui.formatters.$fmt.apply(null, args);
};
} else if (field.editor && field.editor.viewer) {
return showEditable();
}
if (_.isFunction(self.template_readonly)) {
template_readonly = self.template_readonly(scope);
}
if (!template_readonly) {
return false;
}
if (_.isString(template_readonly)) {
template_readonly = axelor.sanitize(template_readonly.trim());
if (template_readonly[0] !== '<' || $(template_readonly).length > 1) {
template_readonly = '<span>' + template_readonly + '</span>';
}
if (field.viewer) {
template_readonly = template_readonly.replace(/^(\s*<\w+)/, '$1 ui-panel-viewer');
}
}
if (!scope.$elem_readonly) {
scope.$elem_readonly = $compile(template_readonly)(scope);
if (self.link_readonly) {
self.link_readonly.call(self, scope, scope.$elem_readonly, attrs, model);
}
}
if (scope.$elem_editable) {
scope.$elem_editable.detach();
}
element.append(scope.$elem_readonly);
return true;
}
scope.$watch("isReadonly()", function isReadonlyWatch(readonly) {
if (readonly && showReadonly()) {
return;
}
return showEditable();
});
scope.$watch("isRequired()", function isRequiredWatch(required, old) {
if (required === old) return;
var elem = element,
label = elem.data('label') || $();
if (label) {
label.toggleClass('required', required);
}
attrs.$set('required', required);
});
if (scope.field && scope.field.validIf) {
scope.$watch("attr('valid')", function attrValidWatch(valid) {
if (valid === undefined) return;
model.$setValidity('invalid', valid);
});
}
scope.$on('$destroy', function() {
if (scope.$elem_editable) {
scope.$elem_editable.remove();
scope.$elem_editable = null;
}
if (scope.$elem_readonly) {
scope.$elem_readonly.remove();
scope.$elem_readonly = null;
}
});
});
return object;
}
return ui.directive(name, ['$compile', function($compile) {
return prepare_templates($compile);
}]);
};
var FormItem = {
css: 'form-item',
cellCss: 'form-item'
};
var FormInput = {
_link_internal: function(scope, element, attrs, model) {
scope.format = function(value) {
return value;
};
scope.parse = function(value) {
return value;
};
scope.validate = function(value) {
return true;
};
scope.setValue = function(value, fireOnChange) {
var val = this.parse(value);
var txt = this.format(value);
var onChange = this.$events.onChange;
model.$setViewValue(val);
this.text = txt;
model.$render();
if (onChange && fireOnChange) {
onChange();
}
};
scope.getValue = function() {
if (model) {
return model.$viewValue;
}
return null;
};
scope.getText = function() {
return this.text;
};
scope.initValue = function(value) {
this.text = this.format(value);
};
model.$render = function() {
scope.initValue(scope.getValue());
if (scope.$render_editable) {
scope.$render_editable();
}
if (scope.$render_readonly) {
scope.$render_readonly();
}
};
// Clear invalid fields (use $setPrestine of angular.js 1.1)
scope.$on('on:new', function(e, rec) {
if (!model.$valid && model.$viewValue) {
model.$viewValue = undefined;
model.$render();
}
});
},
link: function(scope, element, attrs, model) {
},
link_editable: function(scope, element, attrs, model) {
scope.$render_editable = function() {
var value = this.format(this.getValue());
element.val(value);
};
function bindListeners() {
var onChange = scope.$events.onChange || angular.noop,
onChangePending = false;
function listener() {
var value = _.str.trim(element.val()) || null;
if (value !== model.$viewValue) {
scope.$applyAsync(function() {
var val = scope.parse(value);
var txt = scope.format(value);
if (scope.$$setEditorValue && !scope.record) { // m2o editor with null value?
scope.$$setEditorValue({}, false);
}
model.$setViewValue(val);
scope.text = txt;
});
onChangePending = true;
}
}
var field = scope.field || {};
if (!field.bind) {
element.bind('input', listener);
}
element.change(listener);
element.blur(function(e){
if (onChangePending) {
onChangePending = false;
setTimeout(onChange);
}
});
}
if (element.is(':input')) {
setTimeout(bindListeners);
// clear input value
if (scope.$$setEditorValue) {
scope.$on('on:edit', function () {
if (model.$viewValue && !scope.record) {
model.$setViewValue(undefined);
scope.$render_editable();
}
});
}
}
scope.$render_editable();
},
link_readonly: function(scope, element, attrs, model) {
},
template_editable: '<input type="text">',
template_readonly: '<span class="display-text">{{text}}</span>',
template: '<span class="form-item-container"></span>'
};
function inherit(array) {
var args = _.chain(array).rest(1).flatten(true).value();
var last = _.last(args);
var base = null;
var obj = {};
_.chain(args).each(function(source, index) {
if (_.isString(source)) {
source = widgets[source];
}
if (index === args.length - 2) {
base = source;
}
_.extend(obj, source);
});
if (!base) {
return obj;
}
function overridden(name) {
return name !== "controller" &&
_.isFunction(last[name]) && !last[name].$inject &&
_.isFunction(base[name]);
}
function override(name, fn){
return function() {
var tmp = this._super;
this._super = base[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
}
for(var name in last) {
if (overridden(name)) {
obj[name] = override(name, obj[name]);
}
}
return obj;
}
ui.formWidget = function(name, object) {
var obj = inherit(arguments);
var widget = _.str.capitalize(name.replace(/^ui/, ''));
var directive = "ui" + widget;
if (obj.metaWidget) {
metaWidgets.push(widget);
}
registry[directive] = directive;
_.each(obj.widgets, function(alias){
registry[alias] = directive;
});
delete obj.widgets;
widgets[widget] = _.clone(obj);
ui.formDirective(directive, obj);
return obj;
};
ui.formItem = function(name, object) {
return ui.formWidget(name, FormItem, _.rest(arguments, 1));
};
ui.formInput = function(name, object) {
return ui.formWidget(name, FormItem, FormInput, _.rest(arguments, 1));
};
ui.getWidget = function(type) {
var name = type,
widget = registry["ui" + name] || registry[name];
if (!widget) {
name = _.str.classify(name);
widget = registry["ui" + name] || registry[name];
}
if (widget) {
widget = widget.replace(/^ui/, '');
return _.chain(widget).underscored().dasherize().value();
}
return null;
};
ui.getWidgetDef = function (name) {
return widgets[name];
};
ui.getMetaWidgets = function () {
return metaWidgets;
};
})();

134
sophal/js/form/form.code.js Normal file
View File

@ -0,0 +1,134 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/* jshint newcap: false */
/* global CodeMirror: true */
"use strict";
var ui = angular.module('axelor.ui');
ui.formInput('CodeEditor', {
css: "code-editor",
metaWidget: true,
link: function(scope, element, attrs, model) {
var editor = null;
var loading = false;
var field = scope.field;
var props = {
autofocus: true,
lineNumbers: true,
tabSize : 2,
indentUnit : 2,
indentWithTabs: false,
theme: field.codeTheme || "default",
extraKeys: {
'Ctrl-F': function () {}
}
};
if (field.mode || field.codeSyntax) {
props.mode = field.mode || field.codeSyntax;
}
if (props.mode === "xml") {
props = _.extend(props, {
foldGutter : true,
gutters : ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
autoCloseBrackets : true,
autoCloseTags : true
});
}
if (field.height) {
element.height(field.height);
}
setTimeout(function () {
props.readOnly = scope.$$readonly;
editor = CodeMirror(element.get(0), props);
model.$render();
readonlySet(props.readOnly);
editor.on("change", changed);
});
scope.$watch('$$readonly', readonlySet);
model.$render = function() {
loading = true;
var val = model.$modelValue;
if (editor) {
editor.setValue(val || "");
editor.clearHistory();
}
loading = false;
};
model.$formatters.push(function (value) {
return value || '';
});
function readonlySet(readonly) {
if (editor) {
editor.setOption('readOnly', _.toBoolean(readonly));
}
}
function changed(instance, changedObj) {
if (loading || !editor) return;
var value = editor.getValue();
if (value !== model.$viewValue) {
model.$setViewValue(value);
}
scope.$applyAsync();
}
function resize() {
if (element[0].offsetHeight === 0) return; // is hidden?
if (editor) {
editor.refresh();
}
element.width('');
}
element.resizable({
handles: 's',
resize: resize
});
scope.$onAdjust(resize);
},
replace: true,
transclude: true,
template_editable: null,
template_readonly: null,
template: '<div ng-transclude></div>'
});
})();

View File

@ -0,0 +1,841 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
// this directive is used as a replacement for ng-transclude directive
// which fails to keep scope hierarchy (see: https://github.com/angular/angular.js/issues/1809)
ui.directive('uiTransclude', function() {
return {
compile: function(tElement, tAttrs, transclude) {
return function(scope, element, attrs, ctrl) {
transclude(scope.$new(), function(clone) {
element.append(clone);
});
};
}
};
});
/**
* The Group widget.
*
*/
ui.formWidget('Group', {
css: 'form-item-group',
cellCss: 'form-item v-align-top',
link: function(scope, element, attrs) {
var props = scope.field;
scope.collapsed = false;
scope.canCollapse = function() {
return props.canCollapse || props.collapseIf;
};
scope.setCollapsed = function(collapsed) {
scope.collapsed = collapsed;
element.children('legend').nextAll(':not(br)')[collapsed ? 'hide' : 'show']();
axelor.$adjustSize();
};
scope.toggle = function() {
scope.collapsed = !scope.collapsed;
scope.setCollapsed(scope.collapsed);
};
scope.$watch("attr('collapse')", function groupCollapseWatch(collapsed) {
scope.setCollapsed(collapsed);
});
// if auto title, then don't show it
if (attrs.title === attrs.field) {
attrs.$set('title', '');
}
if (props.showTitle !== false) {
scope.$watch('attr("title")', function groupTitleWatch(value){
scope.title = value;
});
}
},
transclude: true,
template:
'<fieldset ng-class="{\'bordered-box\': title, \'has-title\': title}" x-layout-selector="&gt; div:first">'+
'<legend ng-show="title">'+
'<i ng-show="canCollapse()" ng-click="toggle()" ng-class="{\'fa fa-plus\': collapsed, \'fa fa-minus\': !collapsed}"></i>'+
'<span ng-bind-html="title"></span></legend>'+
'<div ui-transclude></div>'+
'</fieldset>'
});
ui.formWidget('Portlet', {
css: 'form-item-portlet',
cellCss: 'form-item v-align-top',
showTitle: false,
link: function(scope, element, attrs) {
var field = scope.field;
scope.canSearch = field.canSearch !== "false";
scope.actionName = field.action;
if (field.name) {
scope.formPath = field.name;
}
if (field.height) {
element.height(field.height);
}
element.resizable({
handles: 's',
resize: _.debounce(function() {
axelor.$adjustSize();
element.width('auto');
}, 100)
});
},
template:
'<div>'+
'<div ui-view-portlet x-action="{{actionName}}" x-can-search="{{canSearch}}"></div>'+
'</div>'
});
ui.formWidget('Dashlet', {
css: 'dashboard',
showTitle: false,
link: function(scope, element, attrs) {
var field = scope.field;
var dashlet = _.extend({}, scope.field);
scope.dashlet = dashlet;
scope.formPath = field.name || field.action;
scope.$watch('attr("title")', function dashletTitleWatch(title, old) {
if (title === old) {
return;
}
var dashletScope = element.children('[ui-view-dashlet]').scope();
if (dashletScope) {
dashletScope.title = title;
}
});
},
template:
'<div>'+
'<div ui-view-dashlet></div>'+
'</div>'
});
/**
* The Tabs widget (notebook).
*/
ui.formWidget('Tabs', {
cellCss: 'form-item v-align-top',
widgets: ['Notebook'],
controller: ['$scope', '$element', function($scope, $element) {
var tabs = $scope.tabs = [],
selected = -1;
var doOnSelectPending = false;
var doOnSelect = _.debounce(function () {
var select = tabs[selected];
if (doOnSelectPending || !select) {
return;
}
doOnSelectPending = true;
$scope.waitForActions(function () {
if (select.handleSelect) {
select.handleSelect();
}
$scope.waitForActions(function () {
doOnSelectPending = false;
});
});
}, 100);
$scope.select = function(tab) {
var current = selected;
angular.forEach(tabs, function(tab, i){
tab.tabSelected = false;
});
tab.tabSelected = true;
selected = _.indexOf(tabs, tab);
if (current === selected) {
return;
}
setTimeout(function() {
if ($scope.$tabs) {
$scope.$tabs.trigger('adjust:tabs');
}
axelor.$adjustSize();
if(current != selected){
doOnSelect();
}
});
};
$scope.$on('on:edit', function (e, record) {
if ($scope.record === record) {
doOnSelect();
}
});
this.addTab = function(tab) {
if (tabs.length === 0) $scope.select(tab);
tab.index = tabs.length;
tabs.push(tab);
};
function inRange(index) {
return index > -1 && index < tabs.length;
}
function findItem(index) {
return $element.find('ul.nav-tabs:first > li:nth-child(' + (index+1) + ')');
}
this.showTab = function(index) {
if (!inRange(index)) {
return;
}
var tab = tabs[index];
var item = findItem(index);
tab.hidden = false;
item.show();
if (selected == -1 || selected === index) {
return $scope.select(tabs[index]);
}
axelor.$adjustSize();
};
this.hideTab = function(index) {
if (!inRange(index))
return;
var item = findItem(index),
tab = tabs[index];
var wasHidden = item.is(":hidden");
item.hide();
item.removeClass('active');
tab.hidden = true;
tab.tabSelected = false;
if (!wasHidden && selected > -1 && selected !== index)
return axelor.$adjustSize();
for(var i = 0 ; i < tabs.length ; i++) {
tab = tabs[i];
if (!tab.hidden) {
return $scope.select(tabs[i]);
}
}
selected = -1;
};
$scope.setTitle = function(value,index){
var item = findItem(index),
pageScope = item.first().data('$scope');
pageScope.tab.title = value;
};
}],
link: function(scope, elem, attrs) {
var props = scope.field;
scope.$tabs = $(elem).bsTabs({
closable: false
});
elem.on('click', '.dropdown-toggle', function(e){
axelor.$adjustSize();
});
// set height (#1011)
if (props.height) {
elem.children('.tab-content:first').height(props.height);
}
},
transclude: true,
template:
'<div class="tabbable-tabs">' +
'<div class="nav-tabs-wrap">' +
'<div class="nav-tabs-scroll-l"><a tabindex="-1" href="#"><i class="fa fa-chevron-left"></i></a></div>' +
'<div class="nav-tabs-scroll-r"><a tabindex="-1" href="#"><i class="fa fa-chevron-right"></i></a></div>' +
'<div class="nav-tabs-strip">' +
'<ul class="nav nav-tabs">' +
'<li tabindex="-1" ng-repeat="tab in tabs" ng-class="{active:tab.tabSelected}">'+
'<a tabindex="-1" href="" ng-click="select(tab)">'+
'<img class="prefix-icon" ng-show="tab.icon" ng-src="{{tab.icon}}">'+
'<span ng-bind-html="tab.title"></span>'+
'</a>' +
'</li>' +
'</ul>' +
'</div>' +
'<div class="nav-tabs-menu">'+
'<div class="dropdown pull-right">'+
'<a class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="caret"></i></a>'+
'<ul class="dropdown-menu" role="menu">'+
'<li ng-repeat="tab in tabs">'+
'<a tabindex="-1" href="javascript: void(0)" ng-click="select(tab)" ng-bind-html="tab.title"></a>'+
'</li>' +
'</ul>' +
'</a>'+
'</div>'+
'</div>'+
'</div>' +
'<div class="tab-content" ui-transclude></div>' +
'</div>'
});
/**
* The Tab widget (notebook page).
*/
ui.formWidget('Tab', {
require: '^uiTabs',
widgets: ['Page'],
handles: ['isHidden'],
link: function(scope, elem, attrs, tabs) {
scope.tabSelected = false;
scope.icon = scope.field && scope.field.icon;
tabs.addTab(scope);
scope.$watch('attr("title")', function tabTitleWatch(value){
scope.title = value;
});
scope.$watch("isHidden()", function tabHiddenWatch(hidden, old) {
if (hidden) {
return tabs.hideTab(scope.index);
}
return tabs.showTab(scope.index);
});
scope.handleSelect = function () {
var onSelect = scope.$events.onSelect;
if (onSelect && !elem.is(":hidden")) {
onSelect();
}
};
},
cellCss: 'form-item v-align-top',
transclude: true,
template: '<div ui-actions class="tab-pane" ng-class="{active: tabSelected}" x-layout-selector="&gt; div:first">'+
'<div ui-transclude></div>'+
'</div>'
});
ui.formWidget('ButtonGroup', {
link: function (scope, element, attrs) {
function adjustButtons() {
var visible = element.children('.btn:visible').length;
if (visible) {
element.children('.btn:visible')
.css('max-width', (100.00/visible) + '%')
.css('width', (100.00/visible) + '%');
}
}
scope.$watch(adjustButtons);
scope.$callWhen(function () {
return element.is(':visible');
}, adjustButtons);
},
transclude: true,
template_editable: null,
template_readonly: null,
template:
"<div class='btn-group' ui-transclude></div>"
});
ui.formWidget('Panel', {
showTitle: false,
link: function (scope, element, attrs) {
var field = scope.field || {};
var body = element.children(".panel-body");
element.addClass(field.serverType);
if (field.sidebar && !attrs.itemSpan) {
attrs.$set('itemSpan', 12, true, 'x-item-span');
}
scope.menus = null;
if (field.menu) {
scope.menus = [field.menu];
}
scope.canCollapse = function() {
return field.canCollapse || field.collapseIf;
};
scope.setCollapsed = function(collapsed) {
var old = scope.collapsed;
var action = collapsed ? "hide" : "show";
scope.collapsed = collapsed;
scope.collapsedIcon = collapsed ? 'fa-chevron-down' : 'fa-chevron-up';
if (collapsed === old) {
return;
}
element.removeClass("collapsed");
body[action]("blind", 200, function () {
element.toggleClass("collapsed", !!collapsed);
if (body.css('display') !== 'none' && action === 'hide') {
body.hide();
}
axelor.$adjustSize();
});
};
scope.toggle = function() {
if (scope.canCollapse()) {
scope.setCollapsed(!scope.collapsed);
}
};
scope.$watch("attr('collapse')", function panelCollapseWatch(collapsed) {
scope.setCollapsed(collapsed);
});
var nested = element.parents('.panel:first').length > 0;
if (nested) {
element.addClass("panel-nested");
}
if (field.showFrame === false) {
element.addClass('noframe');
}
scope.notitle = field.showFrame === false || field.showTitle === false;
scope.title = field.title;
scope.$watch('attr("title")', function panelTitleWatch(title, old) {
if (title === undefined || title === old) return;
scope.title = title;
});
var icon = field.icon;
var iconBg = field.iconBackground;
if (icon && icon.indexOf('fa-') === 0) {
scope.icon = icon;
} else if (icon) {
scope.image = icon;
}
if (scope.icon && iconBg) {
setTimeout(function() {
var iconElem = element.children('.panel-header').children('.panel-icon');
if (iconBg.indexOf("#") === 0) {
iconElem.css('background-color', iconBg);
} else {
iconElem.addClass('bg-' + iconBg);
}
iconElem.addClass('has-bg');
iconElem.find('i').addClass('fg-white');
});
}
setTimeout(function () {
var nestedJson = element.parents('.panel-json:first').length > 0;
if (nestedJson) {
element.removeClass("panel-nested");
}
});
},
transclude: true,
template:
"<div class='panel panel-default'>" +
"<div class='panel-header' ng-click='toggle()' ng-if='!notitle &amp;&amp; field.title' ng-class=\"{'clickable-header' : canCollapse()}\" tabindex='-1'>" +
"<div class='panel-icon' ng-if='icon'><i class='fa' ng-class='icon'></i></div>" +
"<img class='panel-image' ng-if='image' ng-src='{{image}}'>" +
"<div class='panel-title'>{{title}}</div>" +
"<div ng-if='menus' ui-menu-bar menus='menus' handler='this'></div>" +
"<div ng-show='canCollapse()' class='panel-icons'>" +
"<a href=''><i class='fa' ng-class='collapsedIcon'></i></a>" +
"</div>" +
"</div>" +
"<div class='panel-body' ui-transclude></div>" +
"</div>"
});
ui.formWidget('PanelStack', {
transclude: true,
template: "<div class='panel-stack'>" +
"<span ui-transclude></span>" +
"</div>"
});
ui.formWidget('PanelTabs', {
link: function (scope, element, attrs) {
scope.tabs = [];
scope.more = null;
element.find('> .tab-content > div').each(function (index) {
var elem = $(this);
var tab = {
title: elem.attr('x-title'),
selected: false,
hidden: false,
elem: elem,
tabItem: $(),
menuItem: $()
};
scope.tabs.push(tab);
});
var selected = null;
var adjustPending = false;
function findTab(tab) {
var found = scope.tabs[tab] || tab;
if (!found || _.isNumber(found)) {
return null;
}
return found;
}
var doOnSelectPending = false;
var doOnSelect = _.debounce(function () {
if (doOnSelectPending || !selected || !selected.elem) {
return;
}
doOnSelectPending = true;
scope.waitForActions(function () {
var elemScope = selected.elem.scope();
if (elemScope.handleSelect) {
elemScope.handleSelect();
}
doOnSelectPending = false;
});
}, 100);
scope.selectTab = function(tab) {
var current = selected;
var found = findTab(tab);
if (!found) {
return;
}
scope.tabs.forEach(function (current) {
current.selected = false;
current.elem.hide();
});
selected = found;
found.selected = true;
found.elem.show();
found.elem
.add(found.tabItem)
.add(found.menuItem)
.addClass('active');
setTimeout(function () {
scope.$broadcast('tab:select');
elemTabs.removeClass('open');
elemMenu.removeClass('open');
axelor.$adjustSize();
if (current != selected) {
doOnSelect();
}
});
};
scope.showTab = function (tab) {
var found = findTab(tab);
if (!found) {
return;
}
adjustPending = true;
found.hidden = false;
found.tabItem.show();
if (!selected || selected === found) {
return scope.selectTab(found);
}
found.elem.hide();
axelor.$adjustSize();
};
scope.hideTab = function (tab) {
var found = findTab(tab);
if (!found) {
return;
}
adjustPending = true;
var wasHidden = found.hidden;
found.hidden = true;
found.selected = false;
found.elem.hide();
found.tabItem.add(found.menuItem).hide().removeClass('active');
if (!wasHidden && selected && selected !== found) {
return axelor.$adjustSize();
}
var tabs = scope.tabs;
for(var i = 0 ; i < tabs.length ; i++) {
tab = tabs[i];
if (!tab.hidden) {
return scope.selectTab(tabs[i]);
}
}
selected = null;
};
var lastWidth = 0;
var lastTab = null;
var elemTabs = $();
var elemMenu = $();
var elemMenuTitle = $();
var elemMenuItems = $();
function setup() {
elemTabs = element.children('.nav-tabs').children('li:not(.dropdown)');
elemMenu = element.children('.nav-tabs').children('li.dropdown');
elemMenuTitle = elemMenu.children('a:first').children('span');
elemMenuItems = elemMenu.find('li');
_.each(scope.tabs, function (tab, index) {
tab.tabItem = $(elemTabs[index]);
tab.menuItem = $(elemMenuItems[index]);
});
}
var setMenuTitle = (function() {
var setActive = _.debounce(function(selected) {
elemMenu.toggleClass('active', !!selected);
});
return function setMenuTitle(selected) {
elemMenu.show();
elemMenuTitle.html(selected && selected.title);
setActive(selected);
};
}());
function adjust() {
if (elemTabs === null || !scope.tabs || element.is(":hidden")) {
return;
}
var parentWidth = element.width() - 2;
if (parentWidth === lastWidth && lastTab === selected && !adjustPending) {
return;
}
lastWidth = parentWidth;
lastTab = selected;
elemTabs.parent().css('visibility', 'hidden');
elemMenu.hide();
// show visible tabs
scope.tabs.forEach(function (tab, i) {
if (tab.hidden) {
$(elemTabs[i]).hide();
} else {
$(elemTabs[i]).show();
}
});
if (elemTabs.parent().width() <= parentWidth) {
elemTabs.parent().css('visibility', '');
return;
}
setMenuTitle(null);
var elem = null;
var index = elemTabs.length;
var selectedIndex = scope.tabs.indexOf(selected);
while (elemTabs.parent().width() > parentWidth) {
elem = $(elemTabs[--index]);
elem.hide();
if (index === selectedIndex) {
setMenuTitle(selected);
}
}
elemMenuItems.hide();
var tab = null;
while(index < scope.tabs.length) {
tab = scope.tabs[index++];
if (!tab.hidden) {
tab.menuItem.show();
}
}
elemTabs.parent().css('visibility', '');
}
var adjusting = false;
scope.$onAdjust(function() {
if (adjusting) { return; }
try {
adjusting = true;
adjust();
} finally {
adjusting = false;
adjustPending = false;
}
}, 10);
scope.$timeout(function() {
setup();
var first = _.find(scope.tabs, function (tab) {
return !tab.hidden;
});
if (first) {
scope.selectTab(first);
}
});
scope.$on('on:edit', function (e, record) {
if (scope.record === record && !doOnSelectPending) {
scope.ajaxStop(doOnSelect, 100);
}
});
scope.$watch(function tabsWatch() {
var hidden = scope.attr('hidden');
if (hidden) return;
// show selected tab only
scope.tabs.forEach(function (tab) {
if (!tab.selected) tab.elem.hide();
});
});
},
transclude: true,
template:
"<div class='panel-tabs tabbable-tabs'>" +
"<ul class='nav nav-tabs nav-tabs-responsive'>" +
"<li ng-repeat='tab in tabs' ng-class='{active: tab.selected}'>" +
"<a tabindex='-1' href='' ng-click='selectTab(tab)' ng-bind-html='tab.title'></a>" +
"</li>" +
"<li class='dropdown' style='display: none'>" +
"<a tabindex='-1' href='' class='dropdown-toggle' data-toggle='dropdown'><span></span><b class='caret'></b></a>" +
"<ul class='dropdown-menu pull-right'>" +
"<li ng-repeat='tab in tabs' ng-class='{active: tab.selected}'>" +
"<a tabindex='-1' href='' ng-click='selectTab(tab)' ng-bind-html='tab.title'></a>" +
"</li>" +
"</ul>" +
"</li>" +
"</ul>" +
"<div class='tab-content' ui-transclude></div>" +
"</div>"
});
ui.formWidget('PanelTab', {
link: function (scope, element, attrs) {
var index = element.parent().children().index(element);
var tab = null;
var isHidden = scope.isHidden;
function findTab() {
return tab || (tab = (scope.tabs||[])[index]) || {};
}
scope.handleSelect = function () {
var onTabSelect = scope.$events.onTabSelect;
if (onTabSelect && !element.is(":hidden")) {
onTabSelect();
}
};
scope.isHidden = function () {
var tab = findTab();
return !tab.selected || isHidden.call(scope);
};
scope.$watch("attr('title')", function tabTitleWatch(value) {
var tab = findTab();
tab.title = value;
});
scope.$watch("attr('hidden')", function tabHiddenWatch(hidden, old) {
scope.$evalAsync(function () {
if (hidden) {
return scope.hideTab(index);
}
return scope.showTab(index);
});
});
}
});
})();

View File

@ -0,0 +1,234 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function () {
"use strict";
var ui = angular.module('axelor.ui');
var currencySymbols = {
en: '\u0024',
fr: '\u20AC'
};
var thousandSeparator = {
en: ',',
fr: ' '
};
function addCurrency(value, symbol) {
if (value && symbol) {
var val = '' + value;
if (axelor.config['user.lang'] === 'fr' ) {
return val.endsWith(symbol) ? val : val + ' ' + symbol;
}
return val.startsWith(symbol) ? val : symbol + val;
}
return value;
}
function canSetNested(record, name) {
if (record && name && name in record) {
return true;
}
if (name) {
var path = name.split('.');
var val = record || {};
var idx = 0;
while (idx < path.length - 1) {
val = val[path[idx++]];
if (!val) return false;
}
}
return true;
}
function findNested(record, name) {
if (record && name && name in record) {
return record[name] === undefined ? null : record[name];
}
if (name) {
var path = name.split('.');
var val = record || {};
var idx = 0;
while (val && idx < path.length) {
val = val[path[idx++]];
}
if (idx === path.length) {
return val;
}
}
return undefined;
}
function setNested(record, name, value) {
if (!record || !name) return record;
var path = name.split('.');
var nested = record;
var idx = -1;
while (++idx < path.length) {
var key = path[idx];
if (idx !== path.length - 1) {
nested = nested[key] || (nested[key] = {});
} else {
nested[key] = value;
}
}
return record;
}
// override angular.js currency filter
ui.filter('currency', function () {
return addCurrency;
});
function formatNumber(field, value, scale) {
var num = +(value);
if ((value === null || value === undefined) && !field.defaultValue) {
return value;
}
if (num === 0 || num) {
var lang = axelor.config['user.lang'];
var tsep = thousandSeparator[lang] || thousandSeparator.en;
return _.numberFormat(num, scale, '.', tsep);
}
return value;
}
ui.findNested = findNested;
ui.setNested = setNested;
ui.canSetNested = canSetNested;
ui.formatters = {
"string": function(field, value, context) {
if (field.translatable && value && context) {
var key = '$t:' + field.name;
return context[key] || value;
}
return value;
},
"integer": function(field, value) {
return formatNumber(field, value);
},
"decimal": function(field, value, context) {
var scale = (field.widgetAttrs||{}).scale || field.scale || 2;
var currency = (field.widgetAttrs||{}).currency || field.currency;
var text = formatNumber(field, value, scale);
if (text && currency) {
text = addCurrency(text, findNested(context, currency));
}
return text;
},
"boolean": function(field, value) {
return value;
},
"duration": function(field, value) {
return ui.formatDuration(field, value);
},
"date": function(field, value) {
return value ? moment(value).format('DD/MM/YYYY') : "";
},
"time": function(field, value) {
return value ? value : "";
},
"datetime": function(field, value) {
return value ? moment(value).format('DD/MM/YYYY HH:mm') : "";
},
"many-to-one": function(field, value) {
return value
? (field.targetName ? value[field.targetName] : (value.name || value.code || value.id || ""))
: "";
},
"one-to-many": function(field, value) {
return value ? '(' + value.length + ')' : "";
},
"many-to-many": function(field, value) {
return value ? '(' + value.length + ')' : "";
},
"selection": function(field, value) {
var cmp = field.type === "integer" ? function(a, b) { return a == b ; } : _.isEqual;
var res = _.find(field.selectionList, function(item){
return cmp(item.value, value);
}) || {};
return res.title;
}
};
ui.formatters["enum"] = ui.formatters.selection;
function findField(scope, name) {
if (scope.field && scope.field.target) {
return ((scope.field.viewer||{}).fields||{})[name]
|| ((scope.field.editor||{}).fields||{})[name];
}
return (scope.viewItems || scope.fields || {})[name];
}
ui.formatters.$image = function (scope, fieldName, imageName) {
var record = scope.record || {};
var model = scope._model;
if (fieldName) {
var field = (scope.fields||{})[fieldName];
if (field && field.target) {
record = record[fieldName] || {};
model = field.target;
}
}
var v = record.version || record.$version || 0;
var n = record.id;
if (n > 0) {
return "ws/rest/" + model + "/" + n + "/" + imageName + "/download?image=true&v=" + v
+ "&parentId=" + scope.record.id + "&parentModel=" + scope._model;
}
return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
};
ui.formatters.$fmt = function (scope, fieldName, fieldValue, record) {
var context = record || scope.record || {};
var value = arguments.length === 2 ? context[fieldName] : fieldValue;
if (value === undefined || value === null) {
return "";
}
var field = findField(scope, fieldName);
if (!field) {
return value;
}
var type = field.selection ? "selection" : field.type;
var formatter = ui.formatters[type];
if (formatter) {
return formatter(field, value, context);
}
return value;
};
})();

View File

@ -0,0 +1,447 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
var BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
var META_FILE = "com.axelor.meta.db.MetaFile";
var META_JSON_RECORD = "com.axelor.meta.db.MetaJsonRecord";
function makeURL(model, field, recordOrId, version, scope) {
var value = recordOrId;
if (!value) return null;
var id = value.id ? value.id : value;
var ver = version;
if (ver === undefined || ver === null) ver = value.version;
if (ver === undefined || ver === null) ver = value.$version;
if (ver === undefined || ver === null) ver = (new Date()).getTime();
if (!id || id <= 0) return null;
var url = "ws/rest/" + model + "/" + id + "/" + field + "/download?v=" + ver;
if (scope && scope.record) {
var parentId = scope.record.id;
if (!parentId && scope.field && scope._jsonContext && scope._jsonContext.$record) {
parentId = scope._jsonContext.$record.id;
}
url += "&parentId=" + parentId + "&parentModel=" + scope._model;
}
return url;
}
ui.makeImageURL = makeURL;
ui.formInput('ImageLink', {
css: 'image-item',
cssClass: 'from-item image-item',
metaWidget: true,
controller: ['$scope', '$element', '$interpolate', function($scope, $element, $interpolate) {
$scope.parseText = function(text) {
if (!text) return BLANK;
if (!text.match(/{{.*?}}/)) {
return text;
}
return $interpolate(text)($scope.record);
};
}],
init: function(scope) {
var field = scope.field;
var width = field.width || 140;
var height = field.height || '100%';
scope.styles = [{
'width': width,
'max-width': '100%',
'max-height': '100%'
}, {
'width': width,
'height': height,
'max-width': '100%',
'max-height': '100%'
}];
if (field.noframe) {
_.extend(scope.styles[1], {
border: 0,
padding: 0,
background: 'none',
boxShadow: 'none'
});
}
},
link_readonly: function(scope, element, attrs, model) {
var image = element.children('img:first');
var update = scope.$render_readonly = function () {
if (scope.isReadonly()) {
image.get(0).src = scope.parseText(model.$viewValue) || BLANK;
}
};
scope.$watch("record.id", update);
scope.$watch("record.version", update);
scope.$watch("isReadonly()", update);
},
template_editable: '<input type="text">',
template_readonly:
'<div ng-style="styles[0]">'+
'<img class="img-polaroid" ng-style="styles[1]">'+
'</div>'
});
ui.formInput('Image', 'ImageLink', {
init: function(scope) {
this._super.apply(this, arguments);
var field = scope.field;
var isBinary = field.serverType === 'binary';
if (!isBinary && field.target !== META_FILE) {
throw new Error("Invalid field type for Image widget.");
}
scope.parseText = function (value) {
return scope.getLink(value);
};
scope.getLink = function (value) {
var record = scope.record || {};
var model = scope._model;
if (value === null) return BLANK;
if (isBinary) {
if (value) {
return value;
}
if (record.id) {
return makeURL(model, field.name, record, undefined, scope) + "&image=true";
}
return BLANK;
}
return value ? makeURL(META_FILE, "content", (value.id || value), value.version || value.$version, scope) : BLANK;
};
},
link_editable: function(scope, element, attrs, model) {
var field = scope.field;
var input = element.children('input:first');
var image = element.children('img:first');
var buttons = element.children('.btn-group');
var isBinary = field.serverType === 'binary';
var timer = null;
input.add(buttons).hide();
element.on('mouseenter', function (e) {
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
buttons.slideDown();
}, 500);
});
element.on('mouseleave', function (e) {
if (timer) {
clearTimeout(timer);
timer = null;
}
buttons.slideUp();
});
scope.doSelect = function() {
input.click();
};
scope.doSave = function() {
var content = image.get(0).src;
if (content) {
window.open(content);
}
};
scope.doRemove = function() {
image.get(0).src = "";
input.val(null);
update(null);
};
input.change(function(e, ui) {
var file = input.get(0).files[0];
var uploadSize = +(axelor.config["file.upload.size"]) || 0;
// reset file selection
input.get(0).value = null;
if (!file) {
return;
}
if(uploadSize > 0 && file.size > 1048576 * uploadSize) {
return axelor.dialogs.say(_t("You are not allow to upload a file bigger than") + ' ' + uploadSize + 'MB');
}
if (!isBinary) {
return doUpload(file);
}
var reader = new FileReader();
reader.onload = function(e) {
update(e.target.result, file.name);
};
reader.readAsDataURL(file);
});
function doUpload(file) {
var ds = scope._dataSource._new(META_FILE);
var value = field.target === META_FILE ? (scope.getValue()||{}) : {};
var record = {
fileName: file.name,
fileType: file.type,
fileSize: file.size,
id: value.id,
version: value.version || value.$version
};
record.$upload = {
file: file
};
ds.save(record).success(function (saved) {
update(saved);
});
}
function doUpdate(value) {
image.get(0).src = scope.getLink(value);
model.$setViewValue(getData(value));
}
function update(value) {
scope.$applyAsync(function() {
doUpdate(value);
});
}
function getData(value) {
if (!value || isBinary) {
return value;
}
return {
id: value.id
};
}
var updateLink = scope.$render_editable = function() {
image.get(0).src = scope.getLink(model.$viewValue);
};
scope.$watch("record.id", updateLink);
scope.$watch("record.version", updateLink);
if (field.accept) {
input.attr('accept', field.accept);
}
},
template_editable:
'<div ng-style="styles[0]" class="image-wrapper">' +
'<input type="file" accept="image/*">' +
'<img class="img-polaroid" ng-style="styles[1]" style="display: inline-block;">' +
'<div class="btn-group">' +
'<button ng-click="doSelect()" class="btn" type="button"><i class="fa fa-arrow-circle-up"></i></button>' +
'<button ng-click="doRemove()" class="btn" type="button"><i class="fa fa-times"></i></button>' +
'</div>' +
'</div>'
});
ui.formInput('Binary', {
css: 'file-item',
cellCss: 'form-item file-item',
link: function(scope, element, attrs, model) {
var field = scope.field;
var input = element.children('input:first').hide();
scope.doSelect = function() {
input.click();
};
scope.doSave = function() {
var record = scope.record;
var model = scope._model;
var url = makeURL(model, field.name, record, undefined, scope);
ui.download(url, record.fileName || field.name);
};
scope.doRemove = function() {
var record = scope.record;
input.val(null);
model.$setViewValue(null);
record.$upload = null;
if(scope._model === META_FILE) {
record.fileName = null;
record.fileType = null;
}
record.fileSize = null;
};
scope.canDownload = function() {
var record = scope.record || {};
if (!record.id) return false;
if (scope._model === META_FILE) {
return !!record.fileName;
}
return true;
};
input.change(function(e) {
var file = input.get(0).files[0];
var record = scope.record;
// reset file selection
input.get(0).value = null;
if (file) {
record.$upload = {
field: field.name,
file: file
};
if(scope._model === META_FILE) {
record.fileName = file.name;
}
scope.$applyAsync(function() {
record.fileType = file.type;
record.fileSize = file.size;
});
}
});
if (field.accept) {
input.attr('accept', field.accept);
}
},
template_readonly: null,
template_editable: null,
template:
'<div>' +
'<input type="file">' +
'<div class="btn-group">' +
'<button ng-click="doSelect()" ng-show="!isReadonly()" class="btn" type="button"><i class="fa fa-arrow-circle-up"></i></button>' +
'<button ng-click="doSave()" ng-show="canDownload()" class="btn" type="button"><i class="fa fa-arrow-circle-down"></i></button>' +
'<button ng-click="doRemove()" ng-show="!isReadonly()" class="btn" type="button"><i class="fa fa-times"></i></button>' +
'</div>' +
'</div>'
});
ui.formInput('BinaryLink', {
css: 'file-item',
cellCss: 'form-item file-item',
metaWidget: true,
link: function(scope, element, attrs, model) {
var field = scope.field;
var input = element.children('input:first').hide();
if (field.target !== META_FILE) {
throw new Error("BinaryLink widget can be used with MetaFile field only.");
}
scope.doSelect = function() {
input.click();
};
scope.doRemove = function() {
input.val(null);
scope.setValue(null, true);
};
scope.canDownload = function() {
var value = model.$viewValue;
return value && value.id > 0;
};
scope.format = function (value) {
if (value) {
return value.fileName;
}
return value;
};
scope.doSave = function() {
var value = model.$viewValue;
var version = value ? (value.version || value.$version) : undefined;
var url = makeURL(META_FILE, "content", value, version, scope);
ui.download(url, scope.text);
};
input.change(function(e) {
var file = input.get(0).files[0];
// reset file selection
input.get(0).value = null;
if (!file) {
return;
}
var ds = scope._dataSource._new(META_FILE);
var value = scope.getValue() || {};
var record = _.extend({
fileName: file.name,
fileType: file.type,
fileSize: file.size
}, {
id: value.id,
version: value.version || value.$version
});
record.$upload = {
file: file
};
ds.save(record).success(function (rec) {
scope.setValue(rec, true);
});
});
if (field.accept) {
input.attr('accept', field.accept);
}
},
template_readonly: null,
template_editable: null,
template:
'<div>' +
'<input type="file">' +
'<div class="btn-group">' +
'<button ng-click="doSelect()" ng-show="!isReadonly()" class="btn" type="button"><i class="fa fa-arrow-circle-up"></i></button>' +
'<button ng-click="doRemove()" ng-show="canDownload() && !isReadonly()" class="btn" type="button"><i class="fa fa-times"></i></button>' +
'</div> ' +
'<a ng-show="text" href="javascript:" ng-click="doSave()">{{text}}</a>' +
'</div>'
});
})();

View File

@ -0,0 +1,228 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
/**
* The Boolean input widget.
*/
ui.formInput('Boolean', {
css: 'boolean-item',
cellCss: 'form-item boolean-item',
link: function (scope, element, attrs, model) {
element.on('click', 'input:not(.no-toggle)', function (e) {
var value = $(e.target).data('value');
var checked = value === undefined ? e.target.checked : value;
scope.setValue(checked, true);
});
Object.defineProperty(scope, '$value', {
get: function () {
return model.$viewValue;
},
set: function(value) {
model.$setViewValue(value);
}
});
},
template_readonly: null,
template_editable:
"<label class='ibox'>" +
"<input type='checkbox' ng-model='$value' ng-disabled='isReadonly()'>" +
"<span class='box'></span>" +
"</label>"
});
/**
* The Boolean widget with label on right.
*/
ui.formInput('InlineCheckbox', 'Boolean', {
css: 'checkbox-inline',
metaWidget: true,
showTitle: false,
link: function (scope, element, attrs, model) {
this._super.apply(this, arguments);
scope.$watch('attr("title")', function booleanTitleWatch(title) {
scope.label = title;
});
},
template_readonly: null,
template_editable:
"<label class='ibox'>" +
"<input type='checkbox' ng-model='$value' ng-disabled='isReadonly()'>" +
"<div class='box'></div>" +
"<span class='title' ui-help-popover>{{label}}</span>" +
"</label>"
});
ui.formInput('Toggle', 'Boolean', {
cellCss: 'form-item toggle-item',
metaWidget: true,
link: function (scope, element, attrs, model) {
this._super.apply(this, arguments);
var field = scope.field;
var icon = element.find('i');
scope.icon = function () {
return model.$viewValue && field.iconActive ? field.iconActive : field.icon;
};
scope.toggle = function () {
var value = !model.$viewValue;
if (scope.setExclusive && field.exclusive) {
scope.setExclusive(field.name, scope.record);
}
scope.setValue(value, true);
};
if (field.help || field.title) {
element.attr('title', field.help || field.title);
}
},
template_readonly: null,
template_editable:
"<button tabindex='-1' class='btn btn-default' ng-class='{active: $value}' ng-click='toggle()'>" +
"<i class='fa {{icon()}}'></i>" +
"</button>"
});
ui.formInput('BooleanSelect', 'Boolean', {
css: 'form-item boolean-select-item',
metaWidget: true,
init: function (scope) {
var field = scope.field;
var trueText = _t((field.widgetAttrs||{}).trueText) || _t('Yes');
var falseText = _t((field.widgetAttrs||{}).falseText) || _t('No');
scope.$items = [trueText, falseText];
scope.$selection = [{ value: trueText, val: true}, { value: falseText, val: false }];
if (field.nullable) {
scope.$selection.unshift({ value: '', val: null });
}
scope.format = function (value) {
if (field.nullable && (value === null || value === undefined)) {
return "";
}
return value ? scope.$items[0] : scope.$items[1];
};
},
link_editable: function (scope, element, attrs, model) {
var input = element.find('input');
var items = scope.$items;
input.autocomplete({
minLength: 0,
source: scope.$selection,
select: function (e, u) {
scope.setValue(u.item.val, true);
scope.$applyAsync();
}
}).click(function (e) {
input.autocomplete("search" , '');
});
scope.doShowSelect = function () {
input.autocomplete("search" , '');
};
scope.$render_editable = function () {
var value = model.$viewValue;
var text = scope.format(value);
input.val(text);
};
scope.$watch('isReadonly()', function booleanReadonlyWatch(readonly) {
input.autocomplete(readonly ? "disable" : "enable");
input.toggleClass('not-readonly', !readonly);
});
},
template: "<span class='form-item-container'></span>",
template_readonly: '<span>{{text}}</span>',
template_editable: "<span class='picker-input'>" +
"<input type='text' readonly='readonly' class='no-toggle'>" +
"<span class='picker-icons picker-icons-1'>" +
"<i class='fa fa-caret-down' ng-click='doShowSelect()'></i>" +
"</span>" +
"</span>"
});
ui.formInput('BooleanRadio', 'BooleanSelect', {
css: 'form-item boolean-radio-item',
metaWidget: true,
link_editable: function (scope, element, attrs, model) {
var field = scope.field;
if (field.direction === "vertical") {
element.addClass("boolean-radio-vertical");
}
var inputName = _.uniqueId('boolean-radio');
var trueInput = $('<input type="radio" data-value="true" name="' + inputName + '">');
var falseInput = $('<input type="radio" data-value="false" name="' + inputName + '">');
var items = scope.$items;
$('<li>').append(
$('<label class="ibox round">')
.append(trueInput)
.append($('<i class="box">'))
.append($('<span class="title">').text(items[0]))
).appendTo(element);
$('<li>').append(
$('<label class="ibox round">')
.append(falseInput)
.append($('<i class="box">'))
.append($('<span class="title">').text(items[1]))
).appendTo(element);
scope.$render_editable = function () {
var value = model.$viewValue || false;
var input = value ? trueInput : falseInput;
input.prop('checked', true);
};
element.on('change', 'input', function (e) {
var value = $(this).data('value') === true;
scope.setValue(value, true);
scope.$applyAsync();
});
},
template_editable: "<ul class='boolean-radio'></ul>"
});
ui.formInput('BooleanSwitch', 'Boolean', {
css: 'form-item',
metaWidget: true,
template_readonly: null,
template_editable:
"<label class='iswitch'>" +
"<input type='checkbox' ng-model='$value' ng-disabled='isReadonly()'>" +
"<span class='box'></span>" +
"</label>"
});
})();

View File

@ -0,0 +1,635 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
var regional = {
monthNames: [
_t('January'),
_t('February'),
_t('March'),
_t('April'),
_t('May'),
_t('June'),
_t('July'),
_t('August'),
_t('September'),
_t('October'),
_t('November'),
_t('December')],
monthNamesShort: [
_t('Jan'),
_t('Feb'),
_t('Mar'),
_t('Apr'),
_t('May'),
_t('Jun'),
_t('Jul'),
_t('Aug'),
_t('Sep'),
_t('Oct'),
_t('Nov'),
_t('Dec')],
dayNames: [
_t('Sunday'),
_t('Monday'),
_t('Tuesday'),
_t('Wednesday'),
_t('Thursday'),
_t('Friday'),
_t('Saturday')],
dayNamesShort : [_t('Sun'), _t('Mon'), _t('Tue'), _t('Wed'), _t('Thu'), _t('Fri'), _t('Sat')],
dayNamesMin : [_t('Su'), _t('Mo'), _t('Tu'), _t('We'), _t('Th'), _t('Fr'), _t('Sa')],
weekHeader : _t('Wk'),
hourText : _t('Hour'),
minuteText : _t('Minute'),
secondText : _t('Second'),
currentText : _t('Now'),
closeText : _t('Done'),
prevText : _t('Prev'),
nextText : _t('Next'),
firstDay: 1
};
// configure datepicker
$.timepicker.setDefaults(regional);
$.datepicker.setDefaults(regional);
// configure moment.js
moment.locale('en', {
months: regional.monthNames,
monthsShort: regional.monthNamesShort,
weekdays: regional.dayNames,
weekdaysShort: regional.dayNamesShort,
weekdaysMin: regional.dayNamesMin,
calendar : {
sameDay : _t('[Today at] LT'),
nextDay : _t('[Tomorrow at] LT'),
nextWeek : _t('dddd [at] LT'),
lastDay : _t('[Yesterday at] LT'),
lastWeek : _t('[Last] dddd [at] LT'),
sameElse : _t('L')
},
relativeTime : {
future : _t("in %s"),
past : _t("%s ago"),
s : _t("a few seconds"),
m : _t("a minute"),
mm : _t("%d minutes"),
h : _t("an hour"),
hh : _t("%d hours"),
d : _t("a day"),
dd : _t("%d days"),
M : _t("a month"),
MM : _t("%d months"),
y : _t("a year"),
yy : _t("%d years")
}
});
// configure ui.mask
function createTwoDigitDefinition( maximum ) {
return function( value ) {
var number = parseInt( value, 10 );
if (value === "" || /\D/.test(value) || _.isNaN(number)) {
return false;
}
// pad to 2 characters
number = ( number < 10 ? "0" : "" ) + number;
if ( number <= maximum ) {
return number;
}
};
}
function yearsDefinition( value ) {
var temp;
// if the value is empty, or contains a non-digit, it is invalid
if ( value === "" || /\D/.test( value ) ) {
return false;
}
temp = parseInt( value, 10 );
// convert 2 digit years to 4 digits, this allows us to type 80 <right>
if ( value.length <= 2 ) {
// before "32" we assume 2000's otherwise 1900's
if ( temp < 10 ) {
return "200" + temp;
} else if ( temp < 32 ) {
return "20" + temp;
} else {
return "19" + temp;
}
}
if ( temp === 0 ) {
return "2000";
}
if ( value.length === 3 ) {
return "0"+value;
}
if ( value.length === 4 ) {
return value;
}
}
$.extend($.ui.mask.prototype.options.definitions, {
"MM": createTwoDigitDefinition( 12 ),
"DD": createTwoDigitDefinition( 31 ),
"YYYY": yearsDefinition,
"HH": createTwoDigitDefinition( 23 ),
"mm": createTwoDigitDefinition( 59 )
});
// datepicker keyboad navigation hack
var _doKeyDown = $.datepicker._doKeyDown;
$.extend($.datepicker, {
_doKeyDown: function(event) {
var inst = $.datepicker._getInst(event.target),
handled = false;
inst._keyEvent = true;
if ($.datepicker._datepickerShowing) {
switch (event.keyCode) {
case 36: // home
$.datepicker._gotoToday(event.target);
handled = true;
break;
case 37: // left
$.datepicker._adjustDate(event.target, -1, "D");
handled = true;
break;
case 38: // up
$.datepicker._adjustDate(event.target, -7, "D");
handled = true;
break;
case 39: // right
$.datepicker._adjustDate(event.target, +1, "D");
handled = true;
break;
case 40: // down
$.datepicker._adjustDate(event.target, +7, "D");
handled = true;
break;
}
if (handled) {
event.ctrlKey = true;
}
} else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
$.datepicker._showDatepicker(this);
handled = true;
}
if (handled) {
event.preventDefault();
event.stopPropagation();
} else {
_doKeyDown(event);
}
}
});
var _updateDatepicker = $.datepicker._updateDatepicker;
$.datepicker._updateDatepicker = function(inst) {
if (!$.datepicker._noUpdate) {
return _updateDatepicker.call($.datepicker, inst);
}
};
var _parseDate = $.datepicker.parseDate;
$.datepicker.parseDate = function (format, value, settings) {
if (_.isString(value) && value.indexOf('_') > -1) {
return;
}
return _parseDate.call($.datepicker, format, value, settings);
};
// suppress annoying logs
$.timepicker.log = function () {};
/**
* The DateTime input widget.
*/
ui.formInput('DateTime', {
css : 'datetime-item',
format: 'DD/MM/YYYY HH:mm',
mask: 'DD/MM/YYYY HH:mm',
widgets: ['Datetime'],
init: function(scope) {
var isDate = this.isDate,
format = this.format;
scope.parse = function(value) {
if (angular.isDate(value)) {
return isDate ? moment(value).startOf('day').format('YYYY-MM-DD') : value.toISOString();
}
return value;
};
scope.format = function(value) {
if (value) {
return moment(value).format(format);
}
return value;
};
},
link_editable: function(scope, element, attrs, model) {
var input = element.children('input:first');
var button = element.find('i:first');
var onChange = scope.$events.onChange;
var props = scope.field;
var isDate = this.isDate;
var isShowing = false;
var lastValue = null;
var options = {
dateFormat: 'dd/mm/yy',
showButtonsPanel: false,
showTime: false,
showOn: null,
beforeShow: function (e, ui) {
lastValue = input.mask("value") || '';
isShowing = true;
},
onClose: function (e, ui) {
lastValue = null;
isShowing = false;
},
onSelect: function(dateText, inst) {
input.mask('value', dateText);
updateModel();
if (!inst.timeDefined) {
input.datetimepicker('hide');
setTimeout(function(){
input.focus().select();
});
}
}
};
if (this.isDate) {
options.showTimepicker = false;
}
input.datetimepicker(options);
input.mask({
mask: this.mask
});
var changed = false;
var rendering = false;
input.on('change', function(e, ui){
if (changed) return;
if (isShowing) {
changed = lastValue !== (input.mask("value") || '');
} else {
changed = !rendering;
}
});
input.on('blur', function() {
if (changed) {
changed = false;
updateModel();
}
});
input.on('grid:check', function () {
updateModel();
});
input.on('keydown', function(e){
if (e.isDefaultPrevented()) {
return;
}
if (e.keyCode === $.ui.keyCode.DOWN) {
input.datetimepicker('show');
e.stopPropagation();
e.preventDefault();
return false;
}
if (e.keyCode === $.ui.keyCode.ENTER && $(this).datepicker("widget").is(':visible')) {
e.stopPropagation();
e.preventDefault();
return false;
}
if (e.keyCode === $.ui.keyCode.ENTER || e.keyCode === $.ui.keyCode.TAB) {
if (changed) updateModel();
}
});
button.click(function(e, ui){
if (scope.isReadonly()) {
return;
}
var mobile = axelor.device.mobile;
if (mobile) {
input.attr('disabled', 'disabled')
.addClass('mobile-disabled-input');
}
input.datetimepicker('show');
if (mobile) {
input.removeAttr('disabled')
.removeClass('mobile-disabled-input');
}
});
scope.$onAdjust('size scroll', function () {
if (isShowing) {
input.datepicker('widget').hide();
input.datetimepicker('hide');
}
});
function updateModel() {
var masked = input.mask("value") || '',
value = input.datetimepicker('getDate') || null,
oldValue = scope.getValue() || null;
if (_.isEmpty(masked)) {
value = null;
}
if (!input.mask("valid")) {
model.$setViewValue(value); // force validation
model.$render();
scope.$applyAsync();
return;
}
value = scope.parse(value);
if (angular.equals(value, oldValue)) {
return;
}
scope.setValue(value, true);
scope.$applyAsync();
}
scope.validate = function(value) {
var minSize = props.minSize === 'now' ? moment() : props.minSize,
maxSize = props.maxSize,
val = moment(value),
valid = true;
var masked = input.mask('value');
if (masked && !input.mask("valid")) {
return false;
}
if(isDate) {
if(minSize) minSize = moment(minSize).startOf('day');
if(maxSize) maxSize = moment(maxSize).startOf('day');
}
else {
if(minSize) minSize = moment(minSize);
if(maxSize) maxSize = moment(maxSize);
}
if(minSize) {
if(!val) return false;
valid = !val.isBefore(minSize) ;
}
if(valid && maxSize) {
if(!val) return true;
valid = !val.isAfter(maxSize) ;
}
return valid;
};
scope.$render_editable = function() {
rendering = true;
try {
var value = scope.getText();
if (value) {
input.mask('value', value);
try {
$.datepicker._noUpdate = true;
$.datepicker._setDateDatepicker(input[0], moment(scope.getValue()).toDate());
input.mask('refresh');
} finally {
$.datepicker._noUpdate = false;
}
} else {
input.mask('value', '');
}
} finally {
rendering = false;
}
};
},
template_editable:
'<span class="picker-input">'+
'<input type="text" autocomplete="off">'+
'<span class="picker-icons">'+
'<i class="fa fa-calendar"></i>'+
'</span>'+
'</span>'
});
ui.formInput('Date', 'DateTime', {
format: 'DD/MM/YYYY',
mask: 'DD/MM/YYYY',
isDate: true
});
ui.formInput('Time', 'DateTime', {
css: 'time-item',
mask: 'HH:mm',
init: function(scope) {
this._super(scope);
scope.parse = function(value) {
return value;
};
scope.format = function(value) {
return value;
};
},
link_editable: function(scope, element, attrs, model) {
element.mask({
mask: this.mask
});
element.change(function(e, ui) {
updateModel();
});
element.on('keydown', function(e){
if (e.isDefaultPrevented()) {
return;
}
if (e.keyCode === $.ui.keyCode.ENTER || e.keyCode === $.ui.keyCode.TAB) {
updateModel();
}
});
element.on('grid:check', function () {
updateModel();
});
scope.validate = function(value) {
return !value || /^(\d+:\d+)$/.test(value);
};
function updateModel() {
var masked = element.mask("value") || '',
value = element.val() || '',
oldValue = scope.getValue() || '';
if (value && !element.mask("valid")) {
return model.$setViewValue(value); // force validation
}
if (_.isEmpty(masked)) {
value = null;
}
value = scope.parse(value);
if (angular.equals(value, oldValue)) {
return;
}
scope.setValue(value, true);
scope.$applyAsync();
}
scope.$render_editable = function() {
var value = scope.getText();
if (value) {
element.mask('value', value);
} else {
element.mask('value', '');
}
};
},
template_editable: '<input type="text">'
});
ui.formInput('RelativeTime', 'DateTime', {
metaWidget: true,
init: function(scope) {
this._super(scope);
scope.isReadonly = function() {
return true;
};
scope.format = function(value) {
if (value) {
return moment(value).fromNow();
}
return "";
};
}
});
function formatDuration(field, value) {
if (!value || !_.isNumber(value)) {
return value;
}
var h = Math.floor(value / 3600);
var m = Math.floor((value % 3600) / 60);
var s = Math.floor((value % 3600) % 60);
h = _.str.pad(h, field.big ? 3 : 2, '0');
m = _.str.pad(m, 2, '0');
s = _.str.pad(s, 2, '0');
var text = h + ':' + m;
if (field.seconds) {
text = text + ':' + s;
}
return text;
}
ui.formatDuration = formatDuration;
ui.formInput('Duration', 'Time', {
metaWidget: true,
mask: '99:mm',
init: function(scope) {
this._super(scope);
var field = scope.field;
var pattern = /^\d+:\d+(:\d+)?$/;
scope.format = function(value) {
return formatDuration(field, value);
};
scope.parse = function(value) {
if (!value || !_.isString(value)) {
return value;
}
if (!pattern.test(value)) {
return null;
}
var parts = value.split(':'),
first = +(parts[0]),
next = +(parts[1]),
last = +(parts[2] || 0);
return (first * 60 * 60) + (next * 60) + last;
};
},
link_editable: function(scope, element, attrs, model) {
var field = scope.field || {},
mask = this.mask;
if (field.big) {
mask = "999:mm";
}
if (field.seconds) {
mask = mask + ":mm";
}
this.mask = mask;
this._super.apply(this, arguments);
scope.validate = function(value) {
return !value || _.isNumber(value);
};
}
});
})();

View File

@ -0,0 +1,499 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
function getStylePopup(element, styles) {
return function($popup, $button) {
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
$.each(styles, function(format, name) {
var $link = $('<a/>').attr('href', '#').html(name).click(
function(event) {
$(element).wysiwyg('shell').format(format).closePopup();
event.stopPropagation();
event.preventDefault();
return false;
});
$list.append($link);
});
$popup.append($list);
};
}
function getFontNamePopup(element, fonts) {
return function($popup, $button) {
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
$.each(fonts, function(font, name) {
var $link = $('<a/>').attr('href', '#').html(name).click(
function(event) {
try {
document.execCommand('styleWithCSS', false, false);
$(element).wysiwyg('shell').fontName(font).closePopup();
} finally {
document.execCommand('styleWithCSS', false, true);
}
event.stopPropagation();
event.preventDefault();
return false;
});
$list.append($link);
});
$popup.append($list);
};
}
function getFontSizePopup(element, sizes) {
return function($popup, $button) {
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
$.each(sizes, function(size, name) {
var $link = $('<a/>').attr('href', '#').html(name).click(
function(event) {
$(element).focus();
try {
document.execCommand('styleWithCSS', false, false);
$(element).wysiwyg('shell').fontSize(size).closePopup();
} finally {
document.execCommand('styleWithCSS', false, true);
}
event.stopPropagation();
event.preventDefault();
return false;
});
$list.append($link);
});
$popup.append($list);
};
}
function getButtons(scope, element) {
var lite = scope.field.lite;
return {
style: lite ? false : {
title: _t('Style'),
image: '\uf1dd',
popup: getStylePopup(element, {
'<p>' : _t('Normal'),
'<pre>' : '<pre>' + _t('Formatted') + '</pre>',
'<blockquote>' : '<blockquote>' + _t('Blockquote') + '</blockquote>',
'<h1>': '<h1>' + _t('Header 1') + '</h1>',
'<h2>': '<h2>' + _t('Header 2') + '</h2>',
'<h3>': '<h3>' + _t('Header 3') + '</h3>',
'<h4>': '<h4>' + _t('Header 4') + '</h4>',
'<h5>': '<h5>' + _t('Header 5') + '</h5>',
'<h6>': '<h6>' + _t('Header 6') + '</h6>'
})
},
fontName: lite ? false : {
title: _t('Font'),
image: '\uf031',
popup: getFontNamePopup(element, {
'"Times New Roman", Times, serif': '<span style="font-family: \"Times New Roman\", Times, serif">Times New Roman</span>',
'Arial, Helvetica, sans-serif': '<span style="font-family: Arial, Helvetica, sans-serif">Arial</span>',
'"Courier New", Courier, monospace': '<span style="font-family: \"Courier New\", Courier, monospace">Courier New</span>',
'Comic Sans, Comic Sans MS, cursive': '<span style="font-family: Comic Sans, Comic Sans MS, cursive">Comic Sans</span>',
'Impact, fantasy': '<span style="font-family: Impact, fantasy">Impact</span>'
})
},
fontSize: lite ? false : {
title: _t('Font size'),
image: '\uf035',
popup: getFontSizePopup(element, {
'1': '<span style="font-size: x-small">' + _t('Smaller') + '</span>',
'2': '<span style="font-size: small">' + _t('Small') + '</span>',
'3': '<span style="font-size: medium">' + _t('Medium') + '</span>',
'4': '<span style="font-size: large">' + _t('Large') + '</span>',
'5': '<span style="font-size: x-large">' + _t('Larger') + '</span>'
})
},
d1: lite ? false : {
html: $('<span class="wysiwyg-toolbar-divider"></span>')
},
bold: {
title: _t('Bold (Ctrl+B)'),
image: '\uf032'
},
italic: {
title: _t('Italic (Ctrl+I)'),
image: '\uf033'
},
underline: {
title: _t('Underline (Ctrl+U)'),
image: '\uf0cd'
},
strikethrough: {
title: _t('Strikethrough (Ctrl+S)'),
image: '\uf0cc'
},
removeformat: {
title: _t('Remove format'),
image: '\uf12d'
},
d2: {
html: $('<span class="wysiwyg-toolbar-divider"></span>')
},
forecolor: lite ? false : {
title: _t('Text color'),
image: '\uf1fc'
},
highlight: lite ? false : {
title: _t('Background color'),
image: '\uf043'
},
d3: lite ? false : {
html: $('<span class="wysiwyg-toolbar-divider"></span>')
},
alignleft: {
title: _t('Left'),
image: '\uf036'
},
aligncenter: {
title: _t('Center'),
image: '\uf037'
},
alignright: {
title: _t('Right'),
image: '\uf038'
},
alignjustify: {
title: _t('Justify'),
image: '\uf039'
},
d4: {
html: $('<span class="wysiwyg-toolbar-divider"></span>')
},
orderedList: {
title: _t('Ordered list'),
image: '\uf0cb'
},
unorderedList: {
title: _t('Unordered list'),
image: '\uf0ca'
},
d6: lite ? false : {
html: $('<span class="wysiwyg-toolbar-divider"></span>')
},
indent: lite ? false : {
title: _t('Indent'),
image: '\uf03c'
},
outdent: lite ? false : {
title: _t('Outdent'),
image: '\uf03b'
},
d7: lite ? false : {
html: $('<span class="wysiwyg-toolbar-divider"></span>')
},
insertimage: lite ? false : {
title: _t('Insert image'),
image: '\uf030'
},
insertlink: lite ? false : {
title: _t('Insert link'),
image: '\uf08e'
},
d8: lite ? false : {
html: $('<span class="wysiwyg-toolbar-divider"></span>')
},
normalize: lite ? false : {
title: _t('Normalize'),
image: '\uf0d0',
click: function () {
scope.normalize();
}
},
showCode: lite ? false : {
title: _t('Code'),
image: '\uf121',
click: function () {
scope.toggleCode();
}
}
};
}
ui.formInput('Html', {
css: "html-item",
metaWidget: true,
init: function(scope) {
scope.parse = function(value) {
return value ? value : null;
};
},
link_editable: function(scope, element, attrs, model) {
this._super(scope, element, attrs, model);
var textElement = element.find('textarea');
var buttons = getButtons(scope, textElement);
var props = scope.field,
minSize = +props.minSize,
maxSize = +props.maxSize,
height = +(scope.field.height) || null;
if (height) {
height = Math.max(100, height);
}
function isParagraph(node) {
return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase());
}
function findParagraph(node) {
if (!node) return null;
if (node.classList && node.classList.contains('wysiwyg-editor')) return null;
if (isParagraph(node)) return node;
return findParagraph(node.parentNode);
}
textElement.wysiwyg({
toolbar: 'top',
buttons: buttons,
submit: {
title: _t('Submit'),
image: '\uf00c'
},
selectImage: _t('Click or drop image'),
placeholderUrl: 'www.example.com',
maxImageSize: [600, 200],
hijackContextmenu: false,
onKeyPress: function(key, character, shiftKey, altKey, ctrlKey, metaKey) {
if (key !== 13 || shiftKey) {
return;
}
var parent = findParagraph(document.getSelection().anchorNode);
if (!parent) {
document.execCommand('formatBlock', false, '<p>');
}
}
});
var shell = textElement.wysiwyg('shell');
var shellElement = $(shell.getElement());
var shellActive = true;
shellElement.addClass('html-content');
if (scope.field.height) {
shellElement.parent().height(height).resizable({
handles: 's',
resize: function () {
shellElement.parent().width('');
}
});
}
function onChange(e) {
var value = shellActive ? shell.getHTML() : textElement.val();
var text = (value || '').trim()
if (text === '<p><br></p>' || text === '<br>') {
value = null;
}
var old = scope.getValue() || null;
var txt = scope.parse(value) || null;
if (old === txt) {
return;
}
scope.setValue(value, true);
scope.$applyAsync();
}
scope.$render_editable = function () {
var value = scope.getValue() || "";
scope.text = scope.format(value);
var current = shellActive ? shell : textElement;
var getter = shellActive ? 'getHTML' : 'val';
var setter = shellActive ? 'setHTML' : 'val';
var html = current[getter]();
if (value !== html) {
current[setter](value);
}
};
textElement.on('input paste change blur', _.debounce(onChange, 100));
textElement.on("focus", _.once(function (e) {
// Chrome and Edge supports this
document.execCommand('defaultParagraphSeparator', false, 'p');
// firefox uses attributes for some commands
document.execCommand('styleWithCSS', false, true);
document.execCommand('insertBrOnReturn', false, false);
}));
scope.toggleCode = function () {
shellActive = !shellActive;
element.parent().find('.wysiwyg-toolbar-icon')
.toggleClass('disabled', !shellActive)
.last().removeClass('disabled');
if (shellActive) {
textElement.hide();
shellElement.show();
shell.setHTML(textElement.val());
} else {
var height = Math.max(100, shellElement.outerHeight());
shellElement.hide();
textElement.show();
if (!scope.field.height) {
textElement.height(height);
}
}
};
scope.normalize = function () {
var html = shell.getHTML();
var div = $('<div>').html(html);
div.find('p').css({
'margin-top': 0,
'margin-bottom': '1em'});
div.find('ol,ul').each(function() {
var el = $(this);
if (el.parents('ol,ul').length) return;
el.css({
'margin-top': 0,
'margin-bottom': '1em'});
});
div.find('blockquote').each(function() {
var el = $(this);
el.css({
'margin': el.parents('blockquote').length ? '0 0 0 2em' : '0 0 1em 2em',
'border': 'none',
'padding': 0
});
});
shellElement.focus();
shell.setHTML(div[0].innerHTML);
div.remove();
};
scope.validate = function(value) {
if (_.isEmpty(value)) {
return true;
}
var length = value.length,
valid = true;
if (minSize) {
valid = length >= minSize;
}
if(valid && maxSize) {
valid = length <= maxSize;
}
return valid;
};
},
template_readonly:
'<div class="form-item-container">'+
'<div class="html-viewer html-content" ui-bind-template x-text="text" x-locals="record" x-live="field.live"></div>'+
'</div>',
template_editable:
'<div class="form-item-container">'+
'<textarea class="html-editor html-content"></textarea>'+
'</div>'
});
ui.directive('uiBindTemplate', ['$interpolate', function($interpolate){
function expand(scope, template) {
if (!template || !template.match(/{{.*?}}/)) {
return template;
}
return $interpolate(template)(scope.locals());
}
return {
terminal: true,
scope: {
locals: "&",
text: "=text",
live: "&"
},
link: function(scope, element, attrs) {
var template = null;
function update() {
var output = expand(scope, template) || "";
element.html(output);
}
scope.$watch("text", function(text, old) {
if (text === template) {
return;
}
template = text;
update();
});
var live = false;
scope.$watch("live()", function(value) {
if (live || !value) {
return;
}
live = true;
scope.$watch("locals()", function(value) {
update();
}, true);
});
}
};
}]);
})();

View File

@ -0,0 +1,431 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.formWidget('PanelJson', {
showTitle: false,
transclude: true,
template: "<div class='panel-json' ui-transclude></div>"
});
ui.formInput('JsonField', 'String', {
showTitle: false,
link: function (scope, element, attrs, model) {
var field = scope.field;
var jsonFields = field.jsonFields || [];
var jsonNames = _.pluck(jsonFields, 'name');
var jsonFix = {};
jsonFields.forEach(function (item) {
if (item.widget && item.showTitle === undefined) {
var widget = ui.getWidgetDef(item.widget);
if (widget) {
item.showTitle = widget.showTitle;
}
}
});
var defaultValues = {};
var parentUnwatch = null;
var selfUnwatch = null;
scope.formPath = scope.formPath ? scope.formPath + "." + field.name : field.name;
scope.record = {};
jsonFields.forEach(function (item) {
if (item.target === 'com.axelor.meta.db.MetaJsonRecord' &&
item.targetName && item.targetName.indexOf('attrs.') === 0) {
jsonFix[item.name] = function (v) {
if (v) {
v[item.targetName.substring(6)] = v[item.targetName];
}
return v;
};
}
if (item.contextField && item.contextFieldValue) {
if (item.showIf === undefined && item.hideIf === undefined && item.hidden) {
return;
}
var condition = "($record." + item.contextField + ".id === " + item.contextFieldValue + ")";
if (item.showIf) condition += " && (" + item.showIf + ")";
if (item.hideIf) condition += " && !(" + item.hideIf + ")";
item.showIf = condition;
item.hideIf = null;
}
});
function getDefaultValues() {
jsonFields.forEach(function (item) {
if (item.defaultValue === undefined) return;
var value = item.defaultValue;
switch(item.type) {
case 'integer':
value = +(value);
break;
case 'date':
case 'datetime':
value = value === 'now' ? new Date() : moment(value).toDate();
break;
}
defaultValues[item.name] = value;
});
return angular.copy(defaultValues);
}
function unwatchParent() {
if (parentUnwatch) {
parentUnwatch();
parentUnwatch = null;
}
}
function unwatchSelf() {
if (selfUnwatch) {
selfUnwatch();
selfUnwatch = null;
}
}
function watchParent() {
unwatchParent();
parentUnwatch = scope.$watch('$parent.record.' + field.name, function jsonParentWatch(value, old) {
if (value === old) return;
onRender();
});
}
function watchSelf() {
unwatchSelf();
selfUnwatch = scope.$watch('record', function jsonRecordWatch(record, old) {
if (record !== old) {
onUpdate();
}
}, true);
}
function format(name, value) {
var func = jsonFix[name];
return func ? func(value) : value;
}
function onUpdate() {
var rec = null;
_.each(scope.record, function (v, k) {
if (k.indexOf('$') === 0 || v === null || v === undefined) return;
if (_.isArray(v)) {
if (v.length == 0) return;
v = v.map(function (x) {
return x.id ? { id: x.id } : x;
});
}
if (rec === null) {
rec = {};
}
rec[k] = format(k, v);
});
unwatchParent();
if (scope.$parent.record[field.name] || rec) {
scope.$parent.record[field.name] = rec ? angular.toJson(rec) : rec;
}
watchParent();
}
function onRender() {
var record = scope.$parent.record || {};
var value = record[field.name];
unwatchSelf();
if (value) {
scope.record = angular.fromJson(value);
} else {
scope.record = getDefaultValues();
if (!_.isEmpty(scope.record)) {
record[field.name] = angular.toJson(scope.record);
}
onUpdate();
}
scope._jsonContext = { '$record': record };
record['$' + field.name] = scope.record;
watchSelf();
}
scope.$on('on:new', onRender);
scope.$on('on:edit', function () {
if (scope.viewType === 'form' || (!scope.viewType && scope._isPopup)) onRender();
});
scope.updateJsonValues = function (values) {
var rec = null;
_.each(values, function (v, k) {
if (jsonNames.indexOf(k) === -1 && scope.fields[k]) {
scope.$parent.record[k] = v;
} else {
if (rec === null) {
rec = {};
}
rec[k] = v;
}
});
if (rec) {
scope.record = _.extend({}, scope.record, rec);
}
};
watchParent();
// hide parent panel if no jsonFields defined
scope.$evalAsync(function () {
var parent = scope.$parent.field || {};
if (parent.type === 'panel' && _.size(parent.items) === 1 && _.isEmpty(field.jsonFields)) {
element.parents('.panel:first').addClass('hide').hide();
}
});
scope.$on('on:update-context', function (e, context) {
if (context && !context[field.name]) {
context[field.name] = angular.toJson(scope.record || {});
}
});
}
});
ui.formInput('JsonRaw', 'String', {
showTitle: false,
link: function (scope, element, attrs, model) {
scope.placeHolderKey = _t('name');
scope.placeHolderVal = _t('value');
scope.items = [];
scope.onAdd = function () {
var last = _.last(scope.items);
if (last && !(_.trim(last.name) && _.trim(last.value))) return;
scope.items.push({});
};
scope.onRemove = function (index) {
if (scope.items.length > 0) {
scope.items.splice(index, 1);
}
};
var unwatch = null;
function doWatch () {
if (unwatch) {
unwatch();
}
unwatch = scope.$watch('items', function jsonItemsWatch(items, old) {
if (items === old) return;
var record = null;
_.each(items, function (item) {
if (!_.trim(item.name) || !_.trim(item.value)) return;
if (record === null) {
record = {};
}
record[item.name] = item.value;
});
model.$setViewValue(record ? angular.toJson(record) : null);
}, true);
}
model.$render = function () {
var value = model.$viewValue;
if (value) {
value = angular.fromJson(value);
} else {
value = {};
}
scope.items = _.map(_.keys(value), function (name) {
return { name: name, value: value[name] || '' };
});
doWatch();
};
},
template_readonly:
"<div class='json-editor'>" +
"<table class='form-layout'>" +
"<tr ng-repeat='(i, item) in items'>" +
"<td class='form-label'>" +
"<strong class='display-text'>{{item.name}}</strong>:" +
"</td>" +
"<td class='form-item'>" +
"<span class='display-text'>{{item.value}}</span>" +
"</td>" +
"</tr>" +
"</table>" +
"</div>",
template_editable:
"<div class='json-editor'>" +
"<table class='form-layout'>" +
"<tr ng-repeat='(i, item) in items'>" +
"<td class='form-item'><span class='form-item-container'>" +
"<input type='text' placeholder='{{placeHolderKey}}' ng-model='item.name'></span>" +
"</td>" +
"<td class='form-item'><span class='form-item-container'>" +
"<input type='text' placeholder='{{placeHolderVal}}' ng-model='item.value'></span>" +
"</td>" +
"<td><a href='' ng-click='onRemove(i)'><i class='fa fa-minus'></i></a></td>" +
"</tr>" +
"</table>" +
"<a href='' ng-click='onAdd()'><i class='fa fa-plus'></i></a>" +
"</div>"
});
ui.formInput('JsonRefSelect', {
css: 'multi-object-select',
controller: ['$scope', 'ViewService', function($scope, ViewService) {
$scope.createElement = function(id, name, selectionList) {
var elemGroup = $('<div ui-group ui-table-layout cols="2" x-widths="150,*"></div>');
var elemSelect = $('<input ui-select showTitle="false">')
.attr("name", name + "$model")
.attr("x-for-widget", id)
.attr("ng-model", "record." + name + ".model");
var elemSelects = $('<div></div>').attr('ng-switch', "record." + name + ".model");
var elemItems = _.map(selectionList, function(s) {
return $('<input ui-json-ref-item ng-switch-when="' + s.value +'">')
.attr('ng-model', 'record.' + name)
.attr('name', name)
.attr('x-target', s.value);
});
elemGroup
.append($('<div></div>').append(elemSelect))
.append(elemSelects.append(elemItems));
return ViewService.compile(elemGroup)($scope);
};
}],
link: function(scope, element, attrs, model) {
this._super.apply(this, arguments);
var name = scope.field.name;
var selectionList = scope.field.selectionList;
scope.fieldsCache = {};
scope.refFireEvent = function (name) {
var handler = scope.$events[name];
if (handler) {
return handler();
}
};
var elem = scope.createElement(element.attr('id'), name, selectionList);
setTimeout(function() {
element.append(elem);
});
scope.$watch("record." + name + ".model", function jsonModelWatch(value, old) {
if (value === old || old === undefined) return;
if (scope.record && scope.record[name]) {
scope.record[name] = _.pick(scope.record[name], 'model');
if (!scope.record[name].model) {
delete scope.record[name];
}
}
});
},
template_editable: null,
template_readonly: null
});
ui.formInput('JsonRefItem', 'ManyToOne', {
showTitle: false,
link: function(scope, element, attrs, model) {
this._super.apply(this, arguments);
if (scope.field.targetName) {
return this._link.apply(this, arguments);
}
var self = this;
var target = element.attr('x-target');
var data = (_.findWhere(scope.$parent.field.selectionList, {value: target})||{}).data || {};
function doLink(fields) {
var name = false,
search = [];
_.each(fields, function(f) {
if (f.nameColumn) name = f.name;
if (f.name === "name") search.push("name");
if (f.name === "code") search.push("code");
});
if (!name && _.contains(search, "name")) {
name = "name";
}
_.extend(scope.field, {
target: scope._model,
targetName: name,
targetSearch: search,
domain: data.domain
});
self._link(scope, element, attrs, model);
}
if (scope.fieldsCache[scope._model]) {
doLink(scope.fieldsCache[scope._model]);
} else {
scope.loadFields().success(function (fields) {
scope.fieldsCache[scope._model] = fields;
doLink(fields);
});
}
},
_link: function(scope, element, attrs, model) {
var name = element.attr('name');
scope.getValue = function () {
return scope.record[name];
};
var __setValue = scope.setValue;
scope.setValue = function (value) {
var val = _.pick(scope.record[name], 'model');
val = _.extend(val, value);
__setValue.call(scope, val);
};
function doSelect() {
var value = (scope.record || {})[name];
scope.select(value);
}
scope.$watch("record", doSelect);
}
});
})();

View File

@ -0,0 +1,252 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/* jshint validthis: true */
"use strict";
var ui = angular.module('axelor.ui');
/**
* The Numeric input widget.
*/
ui.formInput('Number', {
css: 'integer-item',
widgets: ['Integer', 'Long', 'Decimal'],
template_readonly: '<span class="display-text">{{localeValue()}}</span>',
link: function(scope, element, attrs, model) {
var props = scope.field,
minSize = +props.minSize,
maxSize = +props.maxSize;
var isDecimal = props.serverType === "decimal" || props.widget === "decimal",
pattern = isDecimal ? /^(-)?\d+(\.\d+)?$/ : /^\s*-?[0-9]*\s*$/;
function scale() {
var value = scope.attr('scale');
if (value) {
return value;
}
if ((props.widgetAttrs||{}).scale) {
return props.widgetAttrs.scale;
}
return props.scale || 2;
}
function precision() {
var value = scope.attr('precision');
if (value) {
return value;
}
if ((props.widgetAttrs||{}).precision) {
return props.widgetAttrs.precision;
}
return props.precision || 18;
}
scope.isNumber = function(value) {
return _.isEmpty(value) || _.isNumber(value) || pattern.test(value);
};
scope.validate = scope.isValid = function(value) {
var valid = scope.isNumber(value);
if (valid && isDecimal && _.isString(value)) {
value = scope.format(value);
valid = _.string.trim(value, '-').length - 1 <= precision();
value = +value;
}
if (valid && (minSize || minSize === 0)) {
valid = value >= minSize;
}
if (valid && (maxSize || maxSize === 0)) {
valid = value <= maxSize;
}
return valid;
};
scope.localeValue = function localeValue() {
var value = scope.getValue();
var field = isDecimal ? _.extend({}, scope.field, {
scale: scale(),
precision: precision()
}) : scope.field;
return isDecimal
? ui.formatters.decimal(field, value)
: ui.formatters.integer(field, value);
};
scope.format = function format(value) {
if (isDecimal && _.isString(value) && value.trim().length > 0) {
return parseFloat(value).toFixed(scale());
}
return value;
};
scope.parse = function(value) {
if (isDecimal) return value;
if (value && _.isString(value)) return +value;
return value;
};
scope.$on("on:attrs-changed", function (e, attr) {
if (attr.name === 'scale' || attr.name === 'precision') {
model.$render();
}
});
},
link_editable: function(scope, element, attrs, model) {
var props = scope.field;
var options = {
step: 1
};
element.on("spin", onSpin);
element.on("spinchange", function(e, row) {
updateModel(element.val());
});
element.on("grid:check", function(e, row) {
updateModel(element.val());
});
var pendingChange = false;
function handleChange(changed) {
var onChange = scope.$events.onChange;
if (onChange && (changed || pendingChange)) {
pendingChange = false;
setTimeout(onChange);
}
}
function equals(a, b) {
if (a === b) return true;
if (angular.equals(a, b)) return true;
if (a === "" && b === undefined) return true;
if (b === "" && a === undefined) return true;
if (a === undefined || b === undefined) return false;
if (a === null || b === null) return false;
if (!scope.isNumber(a) || !scope.isNumber(b)) return false;
a = a === "" ? a : ((+a) || 0);
b = b === "" ? b : ((+b) || 0);
return a === b;
}
function updateModel(value, handle) {
if (!scope.isNumber(value)) {
return model.$setViewValue(value); // force validation
}
var val = scope.parse(value);
var old = scope.getValue();
var text = scope.format(value);
element.val(text);
if (equals(val, old)) {
return handleChange();
}
scope.setValue(val);
scope.$applyAsync();
pendingChange = true;
if (handle !== false) {
handleChange();
}
}
function onSpin(event, ui) {
var text = this.value,
value = ui.value,
orig = element.spinner('value'),
parts, integer, decimal, min, max, dir = 0;
event.preventDefault();
if (!scope.isNumber(text)) {
return false;
}
if (value < orig)
dir = -1;
if (value > orig)
dir = 1;
parts = text.split(/\./);
integer = +parts[0];
decimal = parts[1];
integer += dir;
if (parts.length > 1) {
value = integer + '.' + decimal;
}
min = options.min;
max = options.max;
if (_.isNumber(min) && value < min)
value = min;
if (_.isNumber(max) && value > max)
value = max;
updateModel(value, false);
}
if (props.minSize !== undefined)
options.min = +props.minSize;
if (props.maxSize !== undefined)
options.max = +props.maxSize;
setTimeout(function(){
element.spinner(options);
scope.$elem_editable = element.parent();
model.$render = function() {
var value = model.$viewValue;
if (value) {
value = scope.format(value);
}
element.val(value);
scope.initValue(value);
};
model.$render();
});
}
});
// fix spinner repeat issue
var oldRepeat = $.ui.spinner.prototype._repeat;
$.ui.spinner.prototype._repeat = function () {
if (this.element.scope().isReadonly()) {
return this._stop();
}
return oldRepeat.apply(this, arguments);
};
})();

View File

@ -0,0 +1,115 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.ProgressMixin = {
css: 'progress-item',
cellCss: 'form-item progress-item',
metaWidget: true,
link_readonly: function(scope, element, attrs, model) {
var field = scope.field || {},
that = this;
scope.$watch("getValue()", function progressValueWatch(value, old) {
var props = that.compute(field, value);
scope.cssClasses = 'progress ' + props.css;
scope.styles = {
width: props.width + '%'
};
scope.css = props.css;
scope.width = props.width;
});
},
compute: function(field, value) {
var max = +(field.max) || 100,
min = +(field.min) || 0;
var colors = [
["r", 24], // 00 - 24 (red)
["y", 49], // 25 - 49 (yellow)
["b", 74], // 50 - 74 (blue)
["g", 100] // 75 - 100 (green)
];
if (field.colors) {
colors = _.chain(field.colors.split(/,/)).invoke('split', /:/).value() || [];
}
colors.reverse();
var styles = {
"r": "progress-danger",
"y": "progress-warning",
"b": "progress-primary",
"g": "progress-success"
};
var width = +(value) || 0;
var css = "progress-striped";
width = (width * 100) / (max - min);
width = Math.min(Math.round(width), 100);
var color = "";
for(var i = 0 ; i < colors.length; i++) {
var c = colors[i][0];
var v = +colors[i][1];
if (width <= v) {
color = styles[c] || "";
}
}
css += " " + color;
if (width < 100) {
css += " " + "active";
}
return {
css: css,
width: width
};
},
template_readonly:
'<div ng-class="cssClasses">'+
'<div class="bar" ng-style="styles"></div>'+
'</div>'
};
/**
* The Progress widget with integer/decimal input.
*
*/
ui.formInput('Progress', 'Integer', _.extend({}, ui.ProgressMixin));
/**
* The Progress widget with selection input.
*
*/
ui.formInput('SelectProgress', 'Select', _.extend({}, ui.ProgressMixin));
})();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/* global Handsontable: true */
"use strict";
var ui = angular.module('axelor.ui');
ui.formInput('Spreadsheet', {
css: "spreadsheet-item",
link: function (scope, element, attrs, model) {
var field = scope.field;
var height = field.height || 580;
var inst;
scope.$timeout(function () {
element.height(height).css({
"position": "relative",
"overflow": "hidden"
});
inst = new Handsontable(element[0], {
colWidths: 60,
rowHeaders: true,
colHeaders: true,
contextMenu: true,
manualColumnResize: true,
manualRowResize: true,
afterChange: function (change, source) {
if (source !== 'loadData') {
update();
}
},
afterCreateCol: update,
afterCreateRow: update,
afterRemoveCol: update,
afterRemoveRow: update
});
model.$render();
});
element.resizable({
handles: 's',
resize: function () {
if (inst) {
inst.render();
}
}
});
function update() {
if (!inst) { return; }
var current = model.$viewValue;
var value = compact(inst.getData());
value = value ? JSON.stringify(value) : value;
if (value === current) {
return;
}
scope.setValue(value, true);
scope.$applyAsync();
}
function compact(items) {
var res = [];
var i;
for (i = 0; i < items.length; i++) {
var item = items[i];
if (Array.isArray(item)) {
item = compact(item);
}
if (item === "" || item === null || item === undefined || item.length === 0) {
continue;
}
res[i] = item;
}
var n = res.length;
for (i = n - 1; i >= 0; i--) {
if (res[i] !== null) {
n = i+1;
break;
}
}
res = res.slice(0, n);
return res.length ? res : null;
}
function fill(data) {
var cols = 0;
var rows = data.length;
var i, row;
for(i = 0; i < data.length; i++) {
row = data[i] || (data[i] = []);
cols = Math.max(row.length, cols);
}
cols = Math.max(50, cols);
rows = Math.max(100, rows);
for(i = 0; i < rows; i++) {
row = data[i] || (data[i] = []);
for (var j = 0; j < cols + 1; j++) {
if (row[j] === undefined) {
row[j] = null;
}
}
}
return data;
}
model.$render = function () {
var value = null;
try {
value = JSON.parse(model.$viewValue) || null;
} catch (e) {
}
if (inst) {
value = fill(value || []);
inst.loadData(value || null);
setTimeout(function () {
inst.render();
}, 300);
}
};
scope.$on("$destroy", function () {
if (inst) {
inst.destroy();
inst = null;
}
});
},
template_editable: null,
template_readonly: null,
template:
"<div></div>"
});
})();

View File

@ -0,0 +1,849 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
var popoverElem = null;
var popoverTimer = null;
function canDisplayPopover(scope, details) {
if (axelor.device.mobile) {
return false;
}
if(!axelor.config['user.technical']) {
return details ? false : scope.field && scope.field.help;
}
return true;
}
function makePopover(scope, element, callback, placement) {
var mode = axelor.config['application.mode'];
var tech = axelor.config['user.technical'];
var doc = $(document);
var table = null;
function addRow(label, text, klass) {
if (table === null) {
table = $('<table class="field-details"></table>');
}
var tr = $('<tr></tr>').appendTo(table);
if (label) {
$('<th></th>').text(label).appendTo(tr);
}
if (klass == null) {
text = '<code>' + text + '</code>';
}
var td = $('<td></td>').html(text).addClass(klass).appendTo(tr);
if (!label) {
td.attr('colspan', 2);
}
return table;
}
element.popover({
html: true,
delay: { show: 1000, hide: 100 },
animate: true,
placement: function() {
if (placement) return placement;
var coord = $(element.get(0)).offset(),
viewport = { height: window.innerHeight, width: window.innerWidth };
if(viewport.height < (coord.top + 100))
return 'top';
if(coord.left > (viewport.width / 2))
return 'left';
return 'right';
},
trigger: 'manual',
container: 'body',
title: function() {
return element.text();
},
content: function() {
if (table) {
table.remove();
table = null;
}
callback(scope, addRow);
if (table) return table;
return "";
}
});
element.on('mouseenter.popover', enter);
element.on('mouseleave.popover', leave);
function selectText(elem) {
var el = $(elem).get(0);
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(el);
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNodeContents(el);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
}
function enter(e, show) {
if (popoverTimer) {
clearTimeout(popoverTimer);
}
popoverTimer = setTimeout(function () {
if (popoverElem === null) {
popoverElem = element;
popoverElem.popover('show');
if (e.ctrlKey) {
selectText(table.find('.field-name,.model-name').get(0));
}
}
var tip = element.data('popover').$tip;
if (tip) {
tip.attr('tabIndex', 0);
tip.css('outline', 'none');
}
}, (e.ctrlKey || show) ? 0 : 1000);
}
function leave(e) {
if (e.ctrlKey) {
doc.off('mousemove.popover');
doc.on('mousemove.popover', leave);
return;
}
if (popoverTimer) {
clearTimeout(popoverTimer);
popoverTimer = null;
}
if (popoverElem) {
popoverElem.popover('hide');
popoverElem = null;
doc.off('mousemove.popover');
}
}
function destroy() {
if (popoverTimer) {
clearTimeout(popoverTimer);
popoverTimer = null;
}
if (element) {
element.off('mouseenter.popover');
element.off('mouseleave.popover');
element.popover('destroy');
element = null;
}
if (table) {
table.remove();
table = null;
}
doc.off('mousemove.popover');
}
element.on('$destroy', destroy);
scope.$on('$destroy', destroy);
}
function setupPopover(scope, element, getHelp, placement) {
if (!canDisplayPopover(scope, false)) {
return;
}
var timer = null;
element.on('mouseenter.help.setup', function (e) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
element.off('mouseenter.help.setup');
element.off('mouseleave.help.setup');
makePopover(scope, element, getHelp, placement);
element.trigger('mouseenter.popover', true);
}, e.ctrlKey ? 0 : 1000);
});
element.on('mouseleave.help.setup $destroy', function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
});
}
ui.directive('uiTabPopover', function() {
function getHelp(scope, addRow) {
var tab = scope.tab || {};
var type = tab.viewType;
var view = _.findWhere(tab.views, {type: type});
var viewScope = tab.$viewScope;
if (viewScope && viewScope.schema) {
view = viewScope.schema;
}
if (tab.action) {
addRow(_t('Action'), tab.action);
}
if (tab.model) {
addRow(_t('Object'), '<code>' + tab.model + '</code>', 'model-name');
}
if (tab.domain) {
addRow(_t('Domain'), tab.domain);
}
if (view && view.name) {
addRow(_t('View'), view.name);
}
}
return function(scope, element, attrs) {
setupPopover(scope, element, getHelp, 'bottom');
};
});
ui.directive('uiHelpPopover', function() {
function getHelp(scope, addRow) {
var field = scope.field;
var text = field.help;
if (text) {
text = text.replace(/\\n/g, '<br>');
addRow(null, text, 'help-text');
}
if(!canDisplayPopover(scope, true)) {
return;
}
if (text) {
addRow(null, '<hr noshade>', 'help-text');
}
var model = scope._model;
if (model === field.target) {
model = scope._parentModel || scope.$parent._model;
}
addRow(_t('Object'), model);
addRow(_t('Field Name'), '<code>' + field.name + '</code>', 'field-name');
addRow(_t('Field Type'), field.serverType);
if (field.type === 'text') {
return;
}
if (field.domain) {
addRow(_t('Filter'), field.domain);
}
if (field.target) {
addRow(_t('Reference'), field.target);
}
var value = scope.$eval('$$original.' + field.name);
var length;
if (value && /-one$/.test(field.serverType)) {
value = _.compact([value.id, value[field.targetName]]).join(',');
value = '(' + value + ')';
}
if (value && field.type === "password") {
value = _.str.repeat('*', value.length);
}
if (value && /^(string|image|binary)$/.test(field.type)) {
length = value.length;
value = _.first(value, 50);
if (length > 50) {
value.push('...');
}
value = value.join('');
}
if (value && /(panel-related|one-to-many|many-to-many)/.test(field.serverType)) {
length = value.length;
value = _.first(value, 5);
value = _.map(value, function(v){
return v.id;
});
if (length > 5) {
value.push('...');
}
value = value.join(', ');
}
addRow(_t('Orig. Value'), value);
}
function doLink(scope, element, attrs) {
var field = scope.field;
if (field == null) {
return;
}
if (field.help && axelor.config['user.noHelp'] !== true) {
if (element.parent('label').length) {
element.parent('label').addClass('has-help');
} else {
element.addClass('has-help');
}
}
setupPopover(scope, element, getHelp);
}
return function(scope, element, attrs) {
var field = scope.field;
if (!_.isEmpty(field)) {
return doLink(scope, element, attrs);
}
var unwatch = scope.$watch('field', function popoverFieldWatch(field, old) {
if (!field) {
return;
}
unwatch();
doLink(scope, element, attrs);
}, true);
};
});
/**
* The Label widget.
*
*/
ui.formItem('Label', {
css: 'label-item',
cellCss: 'form-label',
transclude: true,
link: function(scope, element, attrs) {
var field = scope.field;
if (field && field.required) {
element.addClass('required');
}
},
template:
"<label><span ui-help-popover ng-transclude></span></label>"
});
ui.directive('uiTranslateIcon', ['$q', function ($q) {
return {
link: function (scope, element) {
var icon = $("<i class='fa fa-flag translate-icon'></i>").attr('title', _t('Show translations.')).appendTo(element);
var toggle = function () {
icon.toggle(!scope.$$readonlyOrig);
};
scope.$watch("$$readonlyOrig", toggle);
scope.$on("on:new", toggle);
scope.$on("on:edit", toggle);
var myDs = scope._dataSource;
var trDs = scope._dataSource._new("com.axelor.meta.db.MetaTranslation");
trDs._sortBy = ["id"];
function saveData(value, data, orig, callback) {
var changed = [];
var removed = [];
data.forEach(function (item) {
var found = _.findWhere(orig, { id: item.id });
if (!angular.equals(found, item)) {
changed.push(item);
}
});
orig.forEach(function (item) {
var found = _.findWhere(data, { id: item.id });
if (!found) {
removed.push(item);
}
});
function saveTranslations() {
var all = [];
if (removed.length) {
all.push(trDs.removeAll(removed));
}
if (changed.length) {
all.push(trDs.saveAll(changed));
}
if (all.length) {
$q.all(all).then(function () {
var lang = axelor.config['user.lang'] || en;
var key = 'value:' + scope.getValue();
var trKey = '$t:' + scope.field.name;
return trDs.search({
domain: "self.key = :key and self.language = :lang",
context: { key: key, lang: lang },
limit: 1
}).success(function (records) {
var record = _.first(records);
if (scope.record) {
scope.record[trKey] = (record||{}).message;
scope.$parent.$parent.text = scope.format(scope.getValue());
var rec = scope._dataSource.get(scope.record.id);
if (rec) {
rec[trKey] = scope.record[trKey];
}
}
});
}).then(callback, callback);
} else {
callback();
}
}
if (value !== scope.getValue()) {
scope.$parent.$parent.setValue(value, true);
scope.waitForActions(function () {
scope.$parent.$parent.onSave().then(saveTranslations, callback);
});
} else {
saveTranslations();
}
}
function showPopup(data) {
if (!data || data.length == 0) {
data = [];
}
var value = scope.getValue();
var orig = angular.copy(data);
var form = $("<form>");
var valueInput = (scope.field.multiline
? $("<textarea class='span12'>")
: $("<input type='text' class='span12'>"))
.prop('name', scope.field.name)
.prop('required', true)
.val(value)
.on('input', function () {
value = this.value;
data.forEach(function (item) {
item.key = 'value:' + value;
});
});
// add value fields
$("<div class='row-fluid'>")
.append($("<label class='span12'>").text(_t("Value")))
.appendTo(form);
$("<div class='row-fluid'>")
.append(valueInput)
.appendTo(form);
form.append('<hr>');
// add translation fields
$("<div class='row-fluid'>")
.append($("<label class='span8'>").text(_t("Translation")))
.append($("<label class='span4'>").text(_t("Language")))
.appendTo(form);
function addRow(item) {
var onchange = function () {
var v = item[this.name];
if (v !== this.value) {
item[this.name] = this.value;
}
};
item.key = item.key || ('value:' + value);
var input1 = (scope.field.multiline
? $("<textarea class='span8'>")
: $("<input type='text' class='span8'>"))
.prop("name", "message")
.prop("required", true)
.val(item.message)
.on("input", onchange);
var input2 = $("<input type='text' class='span4'>")
.prop("name", "language")
.prop("required", true)
.val(item.language)
.on("input", onchange);
var row = $("<div class='row-fluid'>")
.append(input1)
.append(input2)
.appendTo(form);
if (dialog) {
input1.focus();
}
// remove icon
$("<i class='fa fa-times'>")
.add('help', _t('Remove'))
.appendTo(row)
.click(function () {
var i = data.indexOf(item);
data.splice(i, 1);
row.remove();
});
}
function addNew() {
var item = {};
data.push(item);
addRow(item);
}
var dialog;
function validate() {
var empty = html.find('input:text[value=""]');
if (empty.length) {
empty.first().focus();
return false;
}
return true;
}
var html = $("<div>").append(form);
// add icon
$("<i class='fa fa-plus'>")
.attr('help', _t('Add'))
.appendTo(html)
.click(function () {
if (validate()) {
addNew();
}
});
data.forEach(addRow);
if (data.length === 0) {
addNew();
}
function close() {
if (dialog) {
dialog.dialog('close');
}
}
dialog = axelor.dialogs.box(html, {
title: _t('Translations'),
buttons: [{
'text' : _t('Cancel'),
'class' : 'btn',
'click' : close
}, {
'text' : _t('OK'),
'class' : 'btn btn-primary',
'click' : function() {
if (validate()) {
saveData(value, data, orig, close);
}
}
}]
}).addClass('translation-form');
}
icon.click(function (e) {
var value = scope.getValue();
if (value) {
trDs.search({
domain: "self.key = :key",
context: { key: 'value:' + value }
}).success(showPopup);
}
});
}
};
}]);
/**
* The Spacer widget.
*
*/
ui.formItem('Spacer', {
css: 'spacer-item',
template: '<div>&nbsp;</div>'
});
/**
* The Separator widget.
*
*/
ui.formItem('Separator', {
css: 'separator-item',
showTitle: false,
template: '<div>{{field.title}}</div>'
});
/**
* The Static Text widget.
*
*/
ui.formItem('Static', {
css: 'static-item',
link: function (scope, element, attrs, ctrl) {
var field = scope.field;
element.html(field.text);
},
template: '<div></div>'
});
/**
* The Static Label widget.
*
*/
ui.formItem('StaticLabel', {
css: 'static-item',
transclude: true,
template: '<label ng-transclude></label>'
});
/**
* The Help Text widget.
*
*/
ui.formItem('Help', {
css: 'help-item',
link: function (scope, element, attrs, ctrl) {
var field = scope.field;
var css = "alert alert-info";
if (field.css && field.css.indexOf('alert-') > -1) {
css = "alert";
}
element.addClass(css).html(field.text);
},
template: '<div></div>'
});
/**
* The button widget.
*/
ui.formItem('Button', {
css: 'button-item',
transclude: true,
link: function(scope, element, attrs, model) {
var field = scope.field || {};
var icon = field.icon || "";
var iconHover = field.iconHover || "";
var isIcon = icon.indexOf('fa-') === 0;
if (isIcon || icon) {
element.prepend(' ');
}
var css = field.css || '';
if (css.indexOf('btn-') > -1 && css.indexOf('btn-primary') === -1) {
element.removeClass('btn-primary');
}
if (field && field.help && axelor.config['user.noHelp'] !== true) {
element.addClass('has-help');
}
if (isIcon) {
var e = $('<i>').addClass('fa').addClass(icon).prependTo(element);
if (iconHover) {
e.hover(function() {
$(this).removeClass(icon).addClass(iconHover);
}, function() {
$(this).removeClass(iconHover).addClass(icon);
});
}
} else if (icon) {
$('<img>').attr('src', icon).prependTo(element);
}
if (!field.title) {
element.addClass("button-icon");
}
if (_.isString(field.link)) {
element.removeClass('btn btn-primary').addClass('btn-link');
element.attr("href", field.link);
}
element.one('mouseover', function () {
element.tooltip({
html: true,
title: function() {
if (field.help) {
return field.help;
}
if (element.innerWidth() < element[0].scrollWidth) {
return field.title;
}
},
delay: { show: 1000, hide: 100 },
container: 'body'
});
element.on("$destroy", function () {
var t = element.data('tooltip');
if (t) {
t.destroy();
t = null;
}
});
});
element.on("click", function(e) {
if (scope.isReadonlyExclusive() || element.hasClass('disabled')) {
return;
}
function enable() {
scope.ajaxStop(function () {
setDisabled(scope.isReadonlyExclusive());
}, 100);
}
function setEnable(p) {
if (p && p.then) {
p.then(enable, enable);
} else {
scope.ajaxStop(enable, 500);
}
}
function doClick() {
setEnable(scope.fireAction("onClick"));
}
setDisabled(true);
if (scope.waitForActions) {
return scope.waitForActions(doClick);
}
return doClick();
});
function setDisabled(disabled) {
if (disabled || disabled === undefined) {
return element.addClass("disabled").attr('tabindex', -1);
}
return element.removeClass("disabled").removeAttr('tabindex');
}
var readonlySet = false;
scope.$watch('isReadonlyExclusive()', function buttonReadonlyWatch(readonly, old) {
if (readonly === old && readonlySet) return;
readonlySet = true;
return setDisabled(readonly);
});
scope.$watch('attr("title")', function buttonTitleWatch(title, old) {
if (!title || title === old) return;
if (element.is('button')) {
return element.html(title);
}
element.children('.btn-text').html(title);
});
scope.$watch('attr("css")', function buttonCssWatch(css, old) {
var curr = css || field.css || 'btn-success';
var prev = old || field.css || 'btn-success';
if (curr !== prev) {
element.removeClass(prev || '').addClass(curr);
}
});
scope.$watch('attr("icon")', function buttonIconWatch(icon, old) {
if (icon === old || (icon && icon.indexOf('fa-') !== 0)) return;
var iconElem = element.find('i.fa:first');
if (iconElem.length == 0) {
iconElem = $('<i>').addClass('fa').prependTo(element.prepend(' '));
}
iconElem.removeClass(old || '').addClass(icon || field.icon || '');
});
},
template: '<a href="" class="btn btn-primary">'+
'<span class="btn-text" ng-transclude></span>'+
'</a>'
});
ui.formItem('InfoButton', 'Button', {
link: function (scope, element, attrs) {
this._super.apply(this, arguments);
var field = scope.field || {};
scope.title = field.title;
scope.$watch('attr("title")', function infoButtonTitleWatch(title, old) {
if (!title || title === old) return;
scope.title = title;
});
Object.defineProperty(scope, 'value', {
get: function () {
return field.currency
? ui.formatters.decimal(field, (scope.record || {})[field.name], scope.record)
: ui.formatters.$fmt(scope, field.name);
}
});
},
replace: true,
template:
"<div class='btn info-button'>" +
"<div class='info-button-data'>" +
"<span class='info-button-value'>{{value}}</span>" +
"<small class='info-button-title'>{{title}}</small>" +
"</div>" +
"</div>"
});
ui.formItem('ToolButton', 'Button', {
getViewDef: function(element) {
return this.btn;
},
link: function(scope, element, attrs) {
this._super.apply(this, arguments);
var field = scope.field;
if (field == null) {
return;
}
scope.title = field.title;
scope.showTitle = field.showTitle !== false;
scope.btn.isHidden = function() {
return scope.isHidden();
};
},
template:
'<button class="btn" ui-show="!isHidden()" name="{{btn.name}}" ui-actions ui-widget-states>' +
'<span ng-show="showTitle">{{title}}</span>' +
'</button>'
});
})();

View File

@ -0,0 +1,206 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
/**
* The String widget.
*/
ui.formInput('String', {
css: 'string-item',
init: function(scope) {
var field = scope.field;
var isReadonly = scope.isReadonly;
var trKey = "$t:" + field.name;
scope.isReadonly = function () {
scope.$$readonlyOrig = isReadonly.apply(this, arguments);
return (scope.record && scope.record[trKey]) || scope.$$readonlyOrig;
};
scope.format = function (value) {
if ((scope.record && scope.record[trKey])) {
return scope.record[trKey];
}
return value;
};
},
link_editable: function(scope, element, attrs, model) {
this._super.apply(this, arguments);
var field = scope.field,
regex = field.pattern ? new RegExp(field.pattern, 'i') : null,
minSize = +(field.minSize),
maxSize = +(field.maxSize);
scope.validate = function(value) {
if (_.isEmpty(value)) {
return true;
}
var length = value.length,
valid = true;
if (minSize) {
valid = length >= minSize;
}
if(valid && maxSize) {
valid = length <= maxSize;
}
if (valid && regex) {
valid = regex.test(value);
}
return valid;
};
},
template_readonly: '<input type="text" ng-show="text" tabindex="-1" readonly="readonly" class="display-text" value="{{text}}">'
});
/**
* The Email input widget.
*/
ui.formInput('Email', {
css: 'email-item',
metaWidget: true,
pattern: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
link: function(scope, element, attrs, model) {
var pattern = this.pattern;
scope.validate = function(value) {
if(_.isEmpty(value)) {
return true;
}
return pattern.test(value);
};
},
template_editable: '<input type="email">',
template_readonly: '<a target="_blank" ng-show="text" href="mailto:{{text}}">{{text}}</a>'
});
/**
* The URL input widget.
*/
ui.formInput('Url', {
css: 'url-item',
metaWidget: true,
template_editable: '<input type="url">',
template_readonly: '<a target="_blank" ng-show="text" href="{{text}}">{{text}}</a>'
});
/**
* The Phone input widget.
*/
ui.formInput('Phone', 'String', {
css: 'phone-item',
template_editable: '<input type="tel">'
});
/**
* The Text input widget.
*/
ui.formInput('Text', {
css: 'text-item',
link_editable: function(scope, element, attrs, model) {
this._super.apply(this, arguments);
var field = scope.field,
textarea = element.get(0);
textarea.rows = parseInt(field.height) || 8;
//Firefox add one more line
if (axelor.browser.mozilla) {
textarea.rows -= 1;
}
var field = scope.field,
regex = field.pattern ? new RegExp(field.pattern, 'i') : null,
minSize = +(field.minSize),
maxSize = +(field.maxSize);
scope.validate = function(value) {
if (_.isEmpty(value)) {
return true;
}
var length = value.length,
valid = true;
if (minSize) {
valid = length >= minSize;
}
if(valid && maxSize) {
valid = length <= maxSize;
}
if (valid && regex) {
valid = regex.test(value);
}
return valid;
};
},
template_editable: '<textarea></textarea >',
template_readonly: '<pre ng-show="text">{{text}}</pre>'
});
ui.formInput('Password', 'String', {
css: 'password-item',
metaWidget: true,
init: function(scope) {
scope.password = function() {
var value = this.getValue() || "";
return _.str.repeat('*', value.length);
};
},
template_readonly: '<input type="password" ng-show="text" tabindex="-1" readonly="readonly" class="display-text" value="{{password()}}"></input>',
template_editable: '<input type="password" autocomplete="new-password">'
});
ui.directive('uiTextareaAutoSize', function () {
return function (scope, element, attrs) {
if (!element.is('textarea')) return;
function resize() {
var diff = element.outerHeight() - element.innerHeight();
element.css('height', 'auto').css('height', element[0].scrollHeight + diff);
}
element.on('focus keyup input', resize);
setTimeout(resize);
};
});
})();

View File

@ -0,0 +1,602 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/* jshint newcap: false */
"use strict";
var ui = angular.module('axelor.ui');
function TableLayout(items, attrs, $scope, $compile) {
var colWidths = attrs.widths,
numCols = +attrs.cols || 4,
curCol = 0,
layout = [[]];
function add(item, label) {
if (item.is('br')) {
curCol = 0;
item.hide();
return layout.push([]);
}
var row = _.last(layout),
cell = null,
colspan = +item.attr('x-colspan') || 1,
rowspan = +item.attr('x-rowspan') || 1;
if (curCol + colspan >= numCols + 1) {
curCol = 0, row = [];
layout.push(row);
}
if (label) {
cell = {};
cell.elem = label;
cell.css = label.attr('x-cell-css');
row.push(cell);
if (rowspan > 1) cell.rowspan = rowspan;
if (colspan > 1) colspan -= 1;
curCol += 1;
}
cell = {};
cell.elem = item;
cell.css = item.attr('x-cell-css');
if (colspan > 1) cell.colspan = colspan;
if (rowspan > 1) cell.rowspan = rowspan;
row.push(cell);
curCol += colspan;
}
if (colWidths && angular.isString(colWidths)) {
colWidths = colWidths.trim().split(/\s*,\s*/);
for(var i = 0 ; i < colWidths.length; i++) {
var width = colWidths[i];
if (/^(\d+)$/.test(width)) width = width + 'px';
if (width == '*') width = 'auto';
colWidths[i] = width;
}
}
items.each(function(){
var el = $(this),
title = el.attr('x-title'),
noTitle = el.attr('x-show-title') == 'false';
var labelScope = el.data('$scope');
if (labelScope) {
labelScope = labelScope.$new();
}
if (numCols > 1 && !noTitle && title) {
var label = $('<label ui-label></label>').html(title).attr('x-for-widget', el.attr('id')),
labelElem = $compile(label)(labelScope || $scope);
el.data('label', labelElem);
return add(el, labelElem);
}
add(el);
});
var table = $('<table class="form-layout"></table>');
function isLabel(cell) {
return cell.css === "form-label" || (cell.elem && cell.elem.is('label,.spacer-item'));
}
function computeWidths(row) {
if (row.length === 1) return null;
var widths = [],
labelCols = 0,
itemCols = 0,
emptyCols = 0;
_.each(row, function(cell) {
if (isLabel(cell)) {
labelCols += (cell.colspan || 1);
} else {
itemCols += (cell.colspan || 1);
}
});
emptyCols = numCols - (labelCols + itemCols);
labelCols += (emptyCols / 2);
itemCols += (emptyCols / 2) + (emptyCols % 2);
var labelWidth = labelCols ? Math.min(50, (12 * labelCols)) / labelCols : 0;
var itemWidth = (100 - (labelWidth * labelCols)) / itemCols;
_.each(row, function(cell, i) {
var width = ((isLabel(cell) ? labelWidth : itemWidth) * (cell.colspan || 1));
widths[i] = width + "%";
});
return widths;
}
_.each(layout, function(row){
var tr = $('<tr></tr>'),
numCells = 0,
widths = colWidths || computeWidths(row);
_.each(row, function(cell, i) {
var el = $('<td></td>')
.addClass(cell.css)
.attr('colspan', cell.colspan)
.attr('rowspan', cell.rowspan)
.append(cell.elem)
.appendTo(tr);
if (_.isArray(widths) && widths[i]) {
el.width(widths[i]);
}
numCells += cell.colspan || 1;
});
// append remaining cells
for (var i = numCells ; i < numCols ; i++) {
$('<td></td>').appendTo(tr).width((widths||[])[i]);
}
tr.appendTo(table);
});
return table;
} //- TableLayout
ui.directive('uiTableLayout', ['$compile', function($compile) {
return function(scope, element, attrs) {
var elem = attrs.layoutSelector ? element.find(attrs.layoutSelector) : element;
var items = elem.children();
var layout = TableLayout(items, attrs, scope, $compile);
var brTags = element.children('br:hidden'); // detach all the <br> tags
scope.$on('$destroy', function(){
brTags.remove();
});
elem.append(layout);
};
}]);
function PanelLayout(items, attrs, $scope, $compile) {
var stacked = attrs.stacked || false,
flexbox = attrs.flexbox || false,
numCols = 12,
numSpan = +(attrs.itemSpan) || 6,
curCol = 0,
canAddRow = !stacked && !flexbox,
rowClass = flexbox ? 'panel-flex' : 'row-fluid',
cellClass = flexbox ? 'flex' : 'span',
layout = [$('<div>').addClass(rowClass)];
function add(item, label) {
var row = _.last(layout),
cell = $('<div>'),
span = +item.attr('x-colspan') || numSpan,
offset = +item.attr('x-coloffset') || 0;
span = Math.min(span, numCols);
if (stacked) {
span = 0;
}
if (curCol + (span + offset) >= numCols + 1 && canAddRow) {
curCol = 0, row = $('<div>').addClass(rowClass);
layout.push(row);
}
if (label) {
label.appendTo(cell);
row.addClass('has-labels');
}
cell.addClass(item.attr('x-cell-css'));
if (span) {
cell.addClass(cellClass + span);
}
if (offset) {
cell.addClass('offset' + offset);
}
cell.append(item);
cell.appendTo(row);
curCol += (span + offset);
}
items.each(function (item, i) {
var el = $(this),
title = el.attr('x-title'),
noTitle = el.attr('x-show-title') == 'false';
var labelScope = el.data('$scope');
if (labelScope) {
labelScope = labelScope.$new();
}
if (!noTitle && title) {
var label = $('<label ui-label></label>').html(title).attr('x-for-widget', el.attr('id')),
labelElem = $compile(label)(labelScope || $scope);
el.data('label', labelElem);
return add(el, labelElem);
}
add(el);
});
var container = $('<div class="panel-layout"></div>').append(layout);
return container;
}
ui.directive('uiPanelLayout', ['$compile', function($compile) {
return {
priority: 1000,
link: function(scope, element, attrs) {
var elem = element.children('[ui-transclude]:first');
var items = elem.children();
var layout = PanelLayout(items, attrs, scope, $compile);
elem.append(layout);
}
};
}]);
function BarLayout(items, attrs, $scope, $compile) {
var main = $('<div class="bar-main">');
var side = $('<div class="bar-side">');
var wrap = $('<div class="bar-wrap">').appendTo(main);
items.each(function(item, i) {
var elem = $(this);
var prop = elem.scope().field || {};
if (elem.attr('x-sidebar')) {
elem.appendTo(side);
} else {
elem.appendTo(wrap);
}
if (prop.attached) {
elem.addClass("attached");
}
});
var row = $('<div class="bar-container">').append(main);
if (side && axelor.device.small) {
side.children().first().prependTo(wrap);
side.children().appendTo(wrap);
}
wrap.children('[ui-panel-mail]').appendTo(main);
if (side.children().length > 0) {
side.appendTo(row.addClass('has-side'));
}
return row;
}
ui.directive('uiBarLayout', ['$compile', function($compile) {
return function(scope, element, attrs) {
var items = element.children();
var layout = BarLayout(items, attrs, scope, $compile);
var schema = scope.schema || {};
var css = null;
scope._isPanelForm = true;
element.append(layout);
element.addClass('bar-layout');
if (element.has('[x-sidebar]').length === 0) {
css = "mid";
}
if (element.is('form') && ["mini", "mid", "large"].indexOf(schema.width) > -1) {
css = scope.schema.width;
}
if (css) {
element.addClass(css + '-form');
}
};
}]);
ui.directive('uiPanelViewer', function () {
return {
scope: true,
link: function (scope, element, attrs) {
var field = scope.field;
var isRelational = /-to-one$/.test(field.type);
if (isRelational) {
Object.defineProperty(scope, 'record', {
enumerable: true,
get: function () {
return (scope.$parent.record||{})[field.name];
}
});
}
}
};
});
ui.directive('uiPanelEditor', ['$compile', 'ActionService', function($compile, ActionService) {
return {
scope: true,
link: function(scope, element, attrs) {
var field = scope.field;
var editor = field.editor;
if (!editor) {
return;
}
function applyAttrs(item, level) {
if (item.showTitle === undefined && !item.items) {
item.showTitle = (editor.widgetAttrs||{}).showTitles !== "false";
}
if (!item.showTitle && !item.items) {
var itemField = (editor.fields||scope.fields||{})[item.name] || {};
item.placeholder = item.placeholder || itemField.placeholder || item.title || itemField.title || item.autoTitle;
}
if (editor.itemSpan && !item.colSpan && !level) {
item.colSpan = editor.itemSpan;
}
if (item.items) {
_.map(item.items, function (x) {
applyAttrs(x, (level||0) + 1);
});
}
}
var items = editor.items || [];
var hasColSpan = false;
var widths = _.map(items, function (item) {
applyAttrs(item);
if (item.colSpan) {
hasColSpan = true;
}
var width = item.width || (item.widgetAttrs||{}).width;
return width ? width : (item.widget === 'toggle' ? 24 : '*');
});
var schema = hasColSpan ? {
cols: 12,
items: items
} : {
cols: items.length,
colWidths: widths,
items: items
};
if (editor.layout !== 'table') {
schema = {
items: [{
type: 'panel',
items: items,
flexbox: editor.flexbox
}]
};
}
scope.fields = editor.fields || scope.fields;
var form = ui.formBuild(scope, schema, scope.fields);
var isRelational = /-to-one$/.test(field.type);
if (isRelational) {
Object.defineProperty(scope, 'record', {
enumerable: true,
get: function () {
return (scope.$parent.record||{})[field.name];
},
set: function (value) {
scope.setValue(value, true);
}
});
Object.defineProperty(scope, '$$original', {
enumerable: true,
get: function () {
return (scope.$parent.$$original||{})[field.name];
},
set: function (value) {}
});
scope.$$setEditorValue = function (value, fireOnChange) {
scope.setValue(value, fireOnChange === undefined ? true: fireOnChange);
};
}
if (field.target) {
scope.getDummyValues = function() {
if (!scope.record) return {};
var fields = _.keys(scope.fields);
var extra = _.chain(scope.fields_view)
.filter(function(f) { return f.name && f.name[0] === '$' && !_.contains(fields, f.name); })
.filter(function(f) { return ['$changed', '$editorModel', '$version', '$fetched', '$fetchedRelated'].indexOf(f) === -1; })
.pluck('name')
.compact()
.value();
if (scope._model === 'com.axelor.auth.db.User') {
extra = extra.filter(function (n) {
return ['change', 'oldPassword', 'newPassword', 'chkPassword'].indexOf(n) === -1;
});
}
return _.pick(scope.record, extra);
};
scope.getContext = function () {
var context = _.extend({}, scope.record);
var dummy = scope.getDummyValues();
context._model = scope._model;
context._parent = scope.$parent.getContext();
return ui.prepareContext(scope._model, context, dummy);
};
scope.$broadcastRecordChange = function () {
scope.$broadcast("on:record-change", scope.record);
};
scope.$on('on:before-save', function watchParentRecord() {
var dummyValues = scope.getDummyValues();
var watcher = scope.$watch('$parent.record', function (record, old) {
if (record === old) return;
var value = (record||{})[field.name];
if (value && dummyValues) {
value = _.extend(value, dummyValues);
}
dummyValues = null;
watcher();
});
});
scope.$watch('record', function (record, old) {
if (record && !record.$editorModel) {
record.$editorModel = scope._model;
}
});
// make sure to fetch missing values
var fetchMissing = function (value) {
var ds = scope._dataSource;
var record = scope.record;
if (value <= 0 || !value || record.$fetched || record.$fetchedRelated) {
return;
}
var missing = _.filter(_.keys(editor.fields), function (name) {
if (!record) return false;
if (name.indexOf('.') === -1) {
return !record.hasOwnProperty(name);
}
var path = name.split('.');
var nested = record;
for (var i = 0; i < path.length - 1; i++) {
nested = nested[path[i]];
if (!nested) {
return false;
}
}
return !nested.hasOwnProperty(path[path.length - 1]);
});
if (missing.length === 0) {
return;
}
record.$fetchedRelated = true;
return ds.read(value, {fields: missing}).success(function(rec) {
var values = _.pick(rec, missing);
record = _.extend(record, values);
});
};
// make sure to trigger record-change with proper record data
var watchRun = function (value, old) {
if (value && value !== old) {
value.$changed = true;
value.version = _.isNumber(value.version) ? value.version : value.$version;
}
if (value) {
// parent form's getContext will check this to prepare context for editor
// to have proper selection flags in nest o2m/m2m
value.$editorModel = scope._model;
fetchMissing(value.id);
}
scope.$applyAsync(function () {
scope.$broadcast("on:record-change", value || {}, true);
});
// if it's an o2m editor, make sure to update values
if (scope.$itemsChanged) {
scope.$itemsChanged();
}
};
scope.$watch('record', _.debounce(watchRun, 100), true);
scope.$timeout(function () {
scope.$broadcast("on:record-change", scope.record || {}, true);
});
}
form = $compile(form)(scope);
form.removeClass('mid-form mini-form').children('div.row').removeClass('row').addClass('row-fluid');
element.append(form);
if (field.target) {
var handler = null;
if (editor.onNew) {
schema.onNew = editor.onNew;
form.data('$editorForm', form);
handler = ActionService.handler(scope, form, {
action: editor.onNew
});
}
scope.$watch('record.id', function editorRecordIdWatch(value, old) {
if (!value && handler) {
handler.onNew();
}
});
}
scope.isValid = function () {
return scope.form && scope.form.$valid;
};
function isEmpty(record) {
if (!record || _.isEmpty(record)) return true;
var values = _.filter(record, function (value, name) {
return !(/[\$_]/.test(name) || value === null || value === undefined);
});
return values.length === 0;
}
scope.$watch(function editorValidWatch() {
if (isRelational && editor.showOnNew === false && !scope.canShowEditor()) {
return;
}
var valid = scope.isValid();
if (!valid && !field.jsonFields && !scope.$parent.isRequired() && isEmpty(scope.record)) {
var errors = (scope.form || {}).$error || {};
valid = !errors.valid;
}
if (scope.setValidity) {
scope.setValidity('valid', valid, scope.record);
element.toggleClass('nested-not-required', valid);
} else {
scope.$parent.form.$setValidity('valid', valid, scope.form);
}
});
scope.$on('$destroy', function () {
if (scope.setValidity) {
scope.setValidity('valid', true);
}
});
}
};
}]);
})();

1209
sophal/js/form/form.mail.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,561 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.RefFieldCtrl = RefFieldCtrl;
function RefFieldCtrl($scope, $element, DataSource, ViewService, initCallback) {
var field = $scope.getViewDef($element),
params = {
model: field.target || $element.attr('x-target'),
views: field.views || {},
domain: field.domain,
context: field.context
},
views = {};
if (field.jsonTarget) {
params.context = _.extend({}, params.context, { jsonModel: field.jsonTarget });
}
if (!$element.is('fieldset')) {
_.each(field.views, function(view){
views[view.type] = view;
});
var formView = null,
gridView = null,
summaryView = null;
if (field.summaryView === "" || field.summaryView === "true") {
summaryView = views.form;
}
if (field.gridView) {
gridView = {
type: 'grid',
name: field.gridView
};
}
if (field.formView) {
formView = {
type: 'form',
name: field.formView
};
}
if (field.summaryView === "" || field.summaryView === "true") {
summaryView = views.form || formView || { type: 'form' };
} else if (field.summaryView) {
summaryView = {
type: "form",
name: field.summaryView
};
}
views.form = formView || views.form;
views.grid = gridView || views.grid;
params.summaryView = angular.copy(summaryView);
params.summaryViewDefault = params.summaryView || views.form;
params.views = _.compact([views.grid, views.form]);
$scope._viewParams = params;
}
ui.ViewCtrl($scope, DataSource, ViewService);
$scope.ngModel = null;
$scope.editorCanSave = true;
$scope.editorCanReload = field.canReload;
if (initCallback) {
initCallback.call(this);
}
var editor = null;
var selector = null;
var embedded = null;
$scope.createNestedEditor = function() {
return null;
};
/**
* Show/Hide the nested editor according to the show parameter, if
* undefined then toggle.
*
*/
$scope.showNestedEditor = function showNestedEditor(show) {
if (!params.summaryView) {
return;
}
if (embedded === null) {
embedded = $scope.createNestedEditor();
}
var es = embedded.data('$scope');
if (es !== null) {
es.visible = (show === undefined ? !es.visible : show);
embedded.toggle(es.visible);
}
return embedded;
};
$scope.showPopupEditor = function(record) {
if (!record && this.isReadonly()) {
return;
}
if (editor == null) {
editor = ViewService.compile('<div ui-editor-popup></div>')($scope);
editor.data('$target', $element);
}
var popup = editor.isolateScope();
popup.show(record);
popup._afterPopupShow = function() {
if (record == null) {
popup.$broadcast("on:new");
}
};
};
function _showEditor(record) {
if (!$scope._isPopup && field.editWindow === "blank" && record && record.id > 0) {
var checkVersion = "" + axelor.config["view.form.check-version"];
var context = ($scope.selectedTab || {}).context || {};
if (context.__check_version !== undefined) {
checkVersion = "" + context.__check_version;
}
var tab = {
action: _.uniqueId('$act'),
title: field.title,
model: field.target,
recordId: record.id,
views: [{
type: 'form',
name: field.formView
}, {
type: 'grid',
name: field.gridView
}]
};
if (checkVersion) {
tab.context = { __check_version: checkVersion };
}
return $scope.$root.openTab(tab);
}
if ($scope.editorCanReload && record && record.id) {
var parent = $scope.$parent;
if (parent && parent.canSave()) {
var opts = {
callOnSave: field.callOnSave
};
return parent.onSave(opts).then(function(){
$scope.showPopupEditor(record);
});
}
}
return $scope.showPopupEditor(record);
}
$scope.showEditor = function(record) {
var perm = record ? "read" : "create";
var id = (record||{}).id;
if (perm === 'read' && (!id || id < 0)) {
return _showEditor(record);
}
return $scope.isPermitted(perm, record, function(){
_showEditor(record);
});
};
$scope.parentReload = function() {
var parent = $scope.$parent;
if (parent) {
parent.reload();
}
};
$scope.showSelector = function() {
if (this.isReadonly()) {
return;
}
function doShow() {
if (selector == null) {
selector = $('<div ui-selector-popup></div>').attr('x-select-mode', $scope.selectMode || "multi");
selector = ViewService.compile(selector)($scope);
selector.data('$target', $element);
}
var popup = selector.isolateScope();
popup._domain = $scope._domain; // make sure that popup uses my domain (#1233)
popup.show();
}
var onSelect = this.$events.onSelect;
if (onSelect) {
onSelect().then(function(){
doShow();
});
} else {
doShow();
}
};
$scope.$on("on:edit", function(record){
var domain = ($scope.field||field).domain;
var context = ($scope.field||field).context;
if (domain !== undefined) $scope._domain = domain;
if (context !== undefined) $scope._context = context;
});
$scope.setDomain = function(domain, context) {
if (domain !== undefined) $scope._domain = domain;
if (context !== undefined) $scope._context = context;
};
$scope.getDomain = function() {
return {
_domain: $scope._domain,
_context: $scope._context
};
};
var fetchDS = (function () {
var fds = null;
return function () {
if (fds) return fds;
var ds = $scope._dataSource;
return fds = DataSource.create(ds._model, {
domain: ds._domain,
context: ds._context
});
};
})();
$scope.fetchData = function(value, success) {
var records = $.makeArray(value),
ids = [];
_.each(records, function(item) {
if (_.isNumber(item)) {
return ids.push(item);
}
if (_.isNumber(item.id) && item.id > 0 &&
_.isUndefined(item.version) &&
_.isUndefined(item.$fetched)) {
return ids.push(item.id);
}
});
if (ids.length === 0) {
return success(value);
}
var fields = $scope.selectFields();
function doFetch(view) {
var domain = "self.id in (:_field_ids)";
var context = _.pick($scope.getContext(), ['id', '_model']);
var sortBy = view.sortBy || view.orderBy;
if (sortBy) {
sortBy = sortBy.split(",");
}
if (view.canMove && fields.indexOf('sequence') === -1) {
fields.push('sequence');
}
context._field = field.name;
context._field_ids = ids;
return fetchDS().search({
fields: fields,
sortBy: fetchDS()._sortBy || sortBy,
archived: true,
limit: -1,
domain: domain,
context: context
}).success(function(records, page){
// only edited records should have version property
var items = _.map(records, function(item){
item.$version = item.version;
item.$fetched = false;
delete item.version;
return item;
});
success(items, page);
});
}
if ($scope.isHidden()) {
return doFetch($scope.view || {});
}
return $scope._viewPromise.then(function(view) {
return doFetch(view || {});
});
};
$scope.fetchSelection = function(request, response) {
var fn = fetchSelection.bind(this);
var onSelect = this.$events.onSelect;
if (onSelect) {
return onSelect(true).then(function() {
return fn(request, response);
});
}
return fn(request, response);
};
function fetchSelection(request, response) {
/* jshint validthis: true */
var field = this.field;
var nameField = field.targetName || 'id',
fields = field.targetSearch || [],
filter = {},
limit = field.limit || (axelor.device.small ? 6 : 10),
sortBy = field.orderBy;
fields = ["id", nameField].concat(fields);
fields = _.chain(fields).compact().unique().value();
_.each(fields, function(name){
if (name !== "id" && request.term) {
filter[name] = request.term;
}
});
var domain = this._domain,
context = this._context;
if (domain !== undefined && this.getContext) {
context = _.extend({}, context, this.getContext());
}
if (sortBy) {
sortBy = sortBy.split(",");
}
var params = {
filter: filter,
fields: fields,
sortBy: sortBy,
limit: limit
};
if (domain !== undefined) {
params.domain = domain;
params.context = context;
}
fetchDS().search(params).success(function(records, page){
var trKey = '$t:' + nameField;
var items = _.map(records, function(record) {
return {
label: record[trKey] || record[nameField],
value: record
};
});
response(items, page);
});
}
$scope.createOnTheFly = function (term, popup, onSaveCallback) {
var field = $scope.field;
var targetFields = null;
var requiredFields = (field.create||"").split(/,\s*/);
function createItem(fields, term, popup) {
var ds = $scope._dataSource,
data = { $forceDirty: true }, missing = false;
_.each(fields, function(field) {
if (field.name === "name") return data["name"] = term;
if (field.name === "code") return data["code"] = term;
if (field.nameColumn) return data[field.name] = term;
if (requiredFields.indexOf(field.name) > -1) {
return data[field.name] = term;
}
if (field.required) {
missing = true;
}
});
if (popup || missing || _.isEmpty(data)) {
return $scope.showPopupEditor(data);
}
return ds.save(data).success(onSaveCallback);
}
if (targetFields) {
return createItem(targetFields, term, popup);
}
return $scope.loadView("form").success(function(fields, view){
targetFields = fields;
return createItem(fields, term, popup);
});
};
$scope.attachTagEditor = function attachTagEditor(scope, element, attrs) {
var field = scope.field;
var input = null;
if (!field.target) {
return;
}
function onTagEdit(e, item) {
var elem = $(e.target);
var field = scope.field;
var value = item[field.targetName];
function onKeyDown(e) {
// enter key
if (e.keyCode === 13) {
item[field.targetName] = input.val();
saveAndSelect(item);
hideEditor();
}
// escape
if (e.keyCode === 27) {
hideEditor();
}
}
function hideEditor(forceSave) {
$(document).off('mousedown.tag-editor');
$(input).off('keydown.tag-editor').hide();
if (forceSave && value !== input.val()) {
item[field.targetName] = input.val();
saveAndSelect(item);
}
}
if (input === null) {
input = $('<input class="tag-editor" type="text">').appendTo(element);
}
input.val(value)
.width(element.width() - 6)
.show().focus()
.position({
my: 'left top',
at: 'left+3 top+3',
of: element
});
$(input).on('keydown.tag-editor', onKeyDown);
$(document).on('mousedown.tag-editor', function (e) {
if (!input.is(e.target)) {
hideEditor(true);
}
});
}
function saveAndSelect(record) {
var ds = scope._dataSource;
var data = _.extend({}, record, {
version: record.version || record.$version
});
ds.save(data).success(function (rec) {
scope.select(rec);
});
}
scope.onTagEdit = onTagEdit;
};
$scope.canSelect = function() {
var canSelect = $scope.attr('canSelect');
if (canSelect !== undefined) return canSelect;
if ($scope.selectEnable !== undefined) return $scope.selectEnable;
return true;
};
$scope.canNew = function() {
return $scope.attr('canNew') !== false;
};
$scope.canEdit = function() {
return !$scope.isReadonly();
};
$scope.canView = function() {
return $scope.attr('canView') !== false;
};
$scope.canRemove = function() {
return $scope.attr('canRemove') !== false;
};
$scope.select = function(value) {
};
$scope.onNew = function() {
$scope.showEditor(null);
};
$scope.onEdit = function() {
};
$scope.onSelect = function() {
$scope.showSelector();
};
$scope.onRemove = function() {
};
var hasPermission = $scope.hasPermission;
$scope.hasPermission = function(perm) {
if (hasPermission && !hasPermission.apply($scope, arguments)) {
return false;
}
if (!field.perms) return true;
var perms = field.perms;
var permitted = perms[perm];
if (!permitted) {
return false;
}
return true;
};
}
})();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,400 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module("axelor.ui");
var NestedForm = {
scope: true,
controller: [ '$scope', '$element', function($scope, $element) {
ui.FormViewCtrl.call(this, $scope, $element);
$scope.onShow = function(viewPromise) {
};
$scope.$$forceWatch = false;
$scope.$$forceCounter = false;
$scope.$setForceWatch = function () {
$scope.$$forceWatch = true;
$scope.$$forceCounter = true;
};
$scope.registerNested($scope);
$scope.show();
}],
link: function(scope, element, attrs, ctrl) {
},
template: '<div ui-view-form x-handler="this"></div>'
};
ui.EmbeddedEditorCtrl = EmbeddedEditorCtrl;
ui.EmbeddedEditorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];
function EmbeddedEditorCtrl($scope, $element, DataSource, ViewService) {
var params = angular.copy($scope._viewParams);
params.views = _.compact([params.summaryView || params.summaryViewDefault]);
$scope._viewParams = params;
ui.ViewCtrl($scope, DataSource, ViewService);
ui.FormViewCtrl.call(this, $scope, $element);
$scope.visible = false;
$scope.onShow = function() {
};
var originalEdit = $scope.edit;
function doEdit(record) {
if (record && record.id > 0 && !record.$fetched) {
$scope.doRead(record.id).success(function(record){
originalEdit(record);
});
} else {
originalEdit(record);
}
}
function doClose() {
if ($scope.isDetailView) {
$scope.edit($scope.getSelectedRecord());
return;
}
$scope.edit(null);
$scope.waitForActions(function () {
$scope.visible = false;
$element.hide();
$element.data('$rel').show();
});
}
$scope.edit = function(record) {
doEdit(record);
$scope.setEditable(!$scope.$parent.$$readonly);
};
$scope.onClose = function() {
$scope.onClear();
doClose();
};
$scope.onOK = function() {
if (!$scope.isValid()) {
return;
}
var record = $scope.record;
if (record) record.$fetched = true;
var event = $scope.$broadcast('on:before-save', record);
if (event.defaultPrevented) {
if (event.error) {
return axelor.dialogs.error(event.error);
}
}
$scope.waitForActions(function () {
$scope.select($scope.record);
$scope.waitForActions(doClose);
});
};
$scope.onAdd = function() {
if (!$scope.isValid() || !$scope.record) {
return;
}
var record = $scope.record;
record.id = null;
record.version = null;
record.$version = null;
$scope.onClear();
function doSelect(rec) {
if (rec) {
$scope.select(rec);
}
return doEdit(rec);
}
if (!$scope.editorCanSave) {
return doSelect(record);
}
$scope.onSave().then(function (rec) {
doSelect(rec);
});
};
$scope.onClear = function() {
if ($scope.$parent.selection) {
$scope.$parent.selection.length = 0;
}
doEdit(null);
};
$scope.canUpdate = function () {
return $scope.record && $scope.record.id;
};
function loadSelected() {
var record = $scope.getSelectedRecord();
if ($scope.isDetailView) {
$scope.edit(record);
}
}
$scope.$on('grid:changed', function(event) {
loadSelected();
});
$scope.$on('on:edit', function(event, record) {
if ($scope.$parent.record === record) {
$scope.waitForActions(loadSelected);
}
});
$scope.$parent.$watch('isReadonly()', function nestedReadonlyWatch(readonly, old) {
if (readonly === old) return;
$scope.setEditable(!readonly);
});
$scope.show();
}
var EmbeddedEditor = {
restrict: 'EA',
css: 'nested-editor',
scope: true,
controller: EmbeddedEditorCtrl,
link: function (scope, element, attrs) {
setTimeout(function () {
var prev = element.prev();
if (prev.is("[ui-slick-grid]")) {
element.zIndex(prev.zIndex() + 1);
}
});
},
template:
'<fieldset class="form-item-group bordered-box" ui-show="visible">'+
'<div ui-view-form x-handler="this"></div>'+
'<div class="btn-toolbar pull-right">'+
'<button type="button" class="btn btn btn-info" ng-click="onClose()" ng-show="isReadonly()"><span x-translate>Back</span></button> '+
'<button type="button" class="btn btn-primary" ng-click="onOK()" ng-show="!isReadonly() && canUpdate()"><span x-translate>OK</span></button>'+
'<button type="button" class="btn btn-primary" ng-click="onAdd()" ng-show="!isReadonly() && !canUpdate()"><span x-translate>Add</span></button> '+
'<button type="button" class="btn btn-danger" ng-click="onClose()" ng-show="!isReadonly()"><span x-translate>Cancel</span></button> '+
'</div>'+
'</fieldset>'
};
ui.NestedEditorCtrl = NestedEditorCtrl;
ui.NestedEditorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];
function NestedEditorCtrl($scope, $element, DataSource, ViewService) {
var params = angular.copy($scope._viewParams);
params.views = _.compact([params.summaryView || params.summaryViewDefault]);
$scope._viewParams = params;
ui.ManyToOneCtrl.call(this, $scope, $element, DataSource, ViewService);
$scope.nested = null;
$scope.registerNested = function(scope) {
$scope.nested = scope;
$scope.$watch("isReadonly()", function nestedReadonlyWatch(readonly) {
scope.setEditable(!readonly);
});
};
}
var NestedEditor = {
restrict: 'EA',
css: 'nested-editor',
require: '?ngModel',
scope: true,
controller: NestedEditorCtrl,
link: function(scope, element, attrs, model) {
function setValidity(nested, valid) {
model.$setValidity('valid', nested.isValid());
if (scope.setValidity) {
scope.setValidity('valid', nested.isValid());
}
}
var configure = _.once(function (nested) {
//FIX: select on M2O doesn't apply to nested editor
var unwatchId = scope.$watch(attrs.ngModel + '.id', function nestedRecordIdWatch(id, old){
if (id === old) {
return;
}
unwatchId();
unwatchId = null;
scope.$applyAsync();
});
var unwatchValid = nested.$watch('form.$valid', function nestedValidWatch(valid, old){
if (valid === old) {
return;
}
unwatchValid();
unwatchValid = null;
setValidity(nested, valid);
});
scope.$on("on:check-nested-values", function (e, value) {
if (nested && value) {
var val = scope.getValue() || {};
if (val.$updatedValues === value) {
_.extend(nested.record, value);
}
}
});
var parentAttrs = scope.$parent.field || {};
if (parentAttrs.forceWatch) {
nested.$$forceWatch = true;
}
});
var unwatch = null;
var original = null;
function nestedEdit(record, fireOnLoad) {
var nested = scope.nested;
var counter = 0;
if (!nested) return;
if (unwatch) unwatch();
original = angular.copy(record);
unwatch = nested.$watch('record', function nestedRecordWatch(rec, old) {
if (counter++ === 0 && !nested.$$forceCounter) {
return;
}
var ds = nested._dataSource;
var name = scope.field.name;
// don't process default values
if (ds.equals(rec, nested.defaultValues)) {
return;
}
if (_.isEmpty(rec)) rec = null;
if (_.isEmpty(old)) old = null;
if (rec == old) {
return;
}
if (rec) {
rec.$dirty = !(rec.id > 0 && ds.equals(rec, original));
}
model.$setViewValue(rec);
setValidity(nested, nested.isValid());
}, true);
return nested.edit(record, fireOnLoad);
}
scope.ngModel = model;
scope.visible = false;
scope.onClear = function() {
scope.$parent.setValue(null, true);
scope.$parent.$broadcast('on:new');
};
scope.onClose = function() {
scope.$parent._isNestedOpen = false;
scope.visible = false;
element.hide();
};
scope.canClose = function() {
return scope.canToggle() && scope.canSelect();
};
attrs.$observe('title', function(title){
scope.title = title;
});
model.$render = function() {
var nested = scope.nested,
promise = nested._viewPromise,
oldValue = model.$viewValue;
function doRender() {
var value = model.$viewValue;
if (oldValue !== value) { // prevent unnecessary onLoad
return;
}
if (!value || !value.id || value.$dirty) {
return nestedEdit(value, false);
}
if (value.$fetched && (nested.record||{}).$fetched) return;
return nested.doRead(value.id).success(function(record){
record.$fetched = true;
value.$fetched = true;
return nestedEdit(_.extend({}, value, record));
});
}
if (nested == null) {
return;
}
promise.then(function() {
configure(nested);
nestedEdit(model.$viewValue, false);
scope.waitForActions(doRender, 100);
});
};
},
template:
'<fieldset class="form-item-group bordered-box" ui-show="visible">'+
'<legend>'+
'<span ng-bind-html="title"></span> '+
'<span class="legend-toolbar" style="display: none;" ng-show="!isReadonly()">'+
'<a href="" tabindex="-1" ng-click="onClear()" title="{{\'Clear\' | t}}" ng-show="canShowIcon(\'clear\')"><i class="fa fa-ban"></i></a> '+
'<a href="" tabindex="-1" ng-click="onSelect()" title="{{\'Select\' | t}}" ng-show="canShowIcon(\'select\')"><i class="fa fa-search"></i></a> '+
'<a href="" tabindex="-1" ng-click="onClose()" title="{{\'Close\' | t}}" ng-show="canClose()"><i class="fa fa-times-circle"></i></a>'+
'</span>'+
'</legend>'+
'<div ui-nested-form></div>'+
'</fieldset>'
};
ui.formDirective('uiNestedEditor', NestedEditor);
ui.formDirective('uiEmbeddedEditor', EmbeddedEditor);
ui.formDirective('uiNestedForm', NestedForm);
})();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,483 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
/**
* The Form widget.
*
*/
ui.formWidget('Form', {
priority: 100,
css: "dynamic-form",
scope: false,
compile: function(element, attrs) {
element.hide();
element.find('[x-field],[data-field]').each(function(){
var elem = $(this),
name = elem.attr('x-field') || elem.attr('data-field');
if (name && elem.attr('ui-button') === undefined) {
if (!elem.attr('ng-model')) {
elem.attr('ng-model', 'record.' + name);
}
if (!elem.attr('ng-required')) {
// always attache a required validator to make
// dynamic `required` attribute change effective
elem.attr('ng-required', false);
}
}
});
return ui.formCompile.apply(this, arguments);
},
link: function(scope, element, attrs, controller) {
element.on('submit', function(e) {
e.preventDefault();
});
scope.$watch('record', function formRecordWatch(rec, old) {
if (element.is(':visible')) {
return;
}
scope.ajaxStop(function() {
element.show();
axelor.$adjustSize();
});
});
}
});
/**
* This directive is used filter $watch on scopes of inactive tabs.
*
*/
ui.directive('uiTabGate', function() {
return {
compile: function compile(tElement, tAttrs) {
return {
pre: function preLink(scope, element, attrs) {
scope.$watchChecker(function(current) {
if (current.$$popupStack.length) return true;
if (current.tabSelected === undefined) {
return !scope.tab || scope.tab.selected === undefined || scope.tab.selected;
}
return current.tabSelected;
});
}
};
}
};
});
/**
* This directive is used to filter $watch on scopes of hidden forms.
*
*/
ui.directive('uiFormGate', function() {
return {
compile: function compile(tElement, tAttrs) {
return {
pre: function preLink(scope, element, attrs) {
var parent = null;
scope.$watchChecker(function(current) {
if (scope.tabSelected === false) {
return false;
}
if (parent === null) {
parent = element.parents('[ui-show]:first');
}
// hack for hidden nested editors (#2173)
if (scope.$$forceWatch) {
return true;
}
return !(parent.hasClass('ui-hide') || parent.hasClass('ui-hide'));
});
}
};
}
};
});
/**
* This directive is used to filter $watch on scopes based on some condition.
*
*/
ui.directive('uiWatchIf', ['$parse', function($parse) {
return {
compile: function compile(tElement, tAttrs) {
return {
pre: function preLink(scope, element, attrs) {
var value = false,
expression = $parse(attrs.uiWatchIf);
scope.$watchChecker(function (current) {
if (current === scope) {
return value = expression(scope);
}
return value;
});
}
};
}
};
}]);
function toBoolean(value) {
if (value && value.length !== 0) {
var v = angular.lowercase("" + value);
value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
} else {
value = false;
}
return value;
}
/**
* This directive is used to speedup uiFormGate.
*/
ui.directive('uiShow', function() {
return {
scope: true, // create new scope to always watch the expression
link: function link(scope, element, attrs) {
scope.$$shouldWatch = true;
scope.$watch(attrs.uiShow, function uiShowWatchAction(value){
var val = toBoolean(value);
element.css({ display: val ? '' : 'none', opacity: 0 }).toggleClass('ui-hide', !val);
if (val) {
element.animate({ opacity: 1 }, 300);
}
});
}
};
});
/**
* This directive is used by view-pane to attach/detach element from DOM tree
*/
ui.directive('uiAttach', function () {
return function (scope, element, attrs) {
var parent = null;
var detachTimer = null;
var uiAttachWatch = function uiAttachWatch(attach) {
var result = toBoolean(attach);
if (result) {
if (parent) {
if (detachTimer) {
clearTimeout(detachTimer);
detachTimer = null;
} else {
element.appendTo(parent);
}
parent = null;
scope.$broadcast('dom:attach');
}
} else {
parent = element.parent();
scope.$broadcast('dom:detach');
detachTimer = setTimeout(function () {
detachTimer = null;
element.detach();
}, 200);
}
};
uiAttachWatch.uiAttachWatch = true;
scope.$watch(attrs.uiAttach, uiAttachWatch, true);
scope.$on('$destroy', function () {
if (detachTimer) {
clearTimeout(detachTimer);
detachTimer = null;
}
if (parent) {
parent = null;
element.remove();
}
});
};
});
/**
* This directive can be used by widget to restore scroll when element is re-attached to DOM tree.
*/
ui.directive('uiAttachScroll', function () {
return function (scope, element, attrs) {
setTimeout(function () {
var elem = element;
var scrollTop = 0;
if (attrs.uiAttachScroll) {
elem = element.find(attrs.uiAttachScroll);
}
elem.on('scroll', function () {
scrollTop = this.scrollTop;
});
function resetScroll() {
elem.scrollTop(scrollTop);
}
scope.$on('dom:attach', resetScroll);
scope.$on('tab:select', resetScroll);
}, 300);
};
});
ui.directive('uiWidgetStates', ['$parse', '$interpolate', function($parse, $interpolate) {
function isValid(scope, name) {
if (!name) return scope.isValid();
var ctrl = scope.form;
if (ctrl) {
ctrl = ctrl[name];
}
if (ctrl) {
return ctrl.$valid;
}
}
function withContext(scope, record) {
var values = _.extend({}, scope._context, scope._jsonContext, record);
return _.extend(values, {
$user: axelor.config['user.login'],
$group: axelor.config['user.group'],
$userId: axelor.config['user.id'],
});
}
function handleCondition(scope, field, attr, condition, negative) {
if (!condition || _.isBoolean(condition)) {
return;
}
scope.$on("on:record-change", function(e, rec, force) {
if (field && field.jsonField) {
handle(scope.record);
} else if (rec === scope.record || force) {
handle(rec);
}
});
scope.$on("on:grid-selection-change", function(e, context) {
if (field && field.jsonField) return;
if (!scope._isDetailsForm) {
handle(context);
}
});
scope.$watch("isReadonly()", watcher);
scope.$watch("isRequired()", watcher);
scope.$watch("isValid()", watcher);
var expr = $parse(condition);
function watcher(current, old) {
var rec = scope.record;
if (rec === undefined && current === old) return;
if (rec === undefined && scope.getContext) {
rec = scope.getContext();
}
handle(rec);
}
function handle(rec) {
var value;
try {
value = !!axelor.$eval(scope, expr, withContext(scope, rec));
} catch (e) {
console.error('FAILED:', condition, e);
}
// defer attr change to allow field init, see RM-14998
scope.$applyAsync(function () {
scope.attr(attr, negative ? !value : value);
});
}
}
function handleHilites(scope, field) {
if (!field || _.isEmpty(field.hilites)) {
return;
}
var hilites = field.hilites || [];
var exprs = _.map(_.pluck(hilites, 'condition'), function (s) { return $parse(s); });
function handle(rec) {
for (var i = 0; i < hilites.length; i++) {
var hilite = hilites[i];
var expr = exprs[i];
var value = false;
try {
value = axelor.$eval(scope, expr, withContext(scope, rec));
} catch (e) {
console.error('FAILED:', hilite, e);
}
if (value) {
return scope.attr('highlight', {
hilite: hilite,
passed: value
});
}
}
return scope.attr('highlight', {});
}
scope.$on("on:record-change", function(e, rec) {
if (rec === scope.record) {
handle(rec);
}
});
}
function handleBind(scope, field) {
if (!field.bind || !field.name) {
return;
}
var expr = $interpolate(field.bind);
var last = null;
function handle(rec) {
var value;
try {
value = expr(withContext(scope, rec));
if (value.length === 0) {
value = null;
}
} catch (e) {
console.error('FAILED:', field.bind, e);
}
if (scope.setValue && scope.record && last !== value) {
scope.setValue(last = value);
}
}
scope.$on("on:record-change", function(e, rec) {
if (field && field.jsonField) {
handle(scope.record);
} else if (rec && rec === scope.record) {
handle(rec);
}
});
}
function handleValueExpr(scope, field) {
if (!field.valueExpr || !field.name) {
return;
}
var expr = $parse(field.valueExpr);
function handle(rec) {
var value;
try {
value = axelor.$eval(scope, expr, withContext(scope, rec));
if (value && value.length === 0) {
value = null;
}
} catch (e) {
console.error('FAILED:', field.valueExpr, e);
}
if (scope.setValue && scope.record) {
scope.setValue(value, false);
}
}
scope.$on("on:record-change", function(e, rec) {
scope.$timeout(function () {
if (field && field.jsonField) {
handle(scope.record);
} else if (rec && rec === scope.record) {
handle(rec);
}
});
});
}
function handleFor(scope, field, attr, conditional, negative) {
if (field[conditional]) {
handleCondition(scope, field, attr, field[conditional], negative);
}
}
function handleForField(scope) {
var field = scope.field;
if (!field) return;
handleFor(scope, field, "valid", "validIf");
handleFor(scope, field, "hidden", "hideIf");
handleFor(scope, field, "hidden", "showIf", true);
handleFor(scope, field, "readonly", "readonlyIf");
handleFor(scope, field, "required", "requiredIf");
handleFor(scope, field, "collapse", "collapseIf");
handleFor(scope, field, "canNew", "canNew");
handleFor(scope, field, "canView", "canView");
handleFor(scope, field, "canEdit", "canEdit");
handleFor(scope, field, "canRemove", "canRemove");
handleFor(scope, field, "canSelect", "canSelect");
handleHilites(scope, field);
handleBind(scope, field);
handleValueExpr(scope, field);
}
function handleForView(scope) {
var field = scope.schema;
if (!field) return;
handleFor(scope, field, "canNew", "canNew");
handleFor(scope, field, "canEdit", "canEdit");
handleFor(scope, field, "canSave", "canSave");
handleFor(scope, field, "canCopy", "canCopy");
handleFor(scope, field, "canDelete", "canDelete");
handleFor(scope, field, "canArchive", "canArchive");
handleFor(scope, field, "canAttach", "canAttach");
}
return function(scope, element, attrs) {
scope.$evalAsync(function() {
if (element.is('[ui-form]')) {
return handleForView(scope);
}
handleForField(scope);
});
};
}]);
})();

240
sophal/js/lib/dialogs.js Normal file
View File

@ -0,0 +1,240 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var dialogs = {
config: {
yesNo: false
},
say: function(str) {
return this.box(str, {
title: _t('Information')
});
},
warn: function(str, callback) {
return this.box(str, {
title: _t('Warning'),
onClose: callback
});
},
error: function(str, callback) {
return this.box(str, {
title: _t('Error'),
onClose: callback
});
},
confirm: function(str, callback, options) {
var element = null,
opts = null,
cb = angular.noop,
doCall = true;
for (var i = 1; i < 3; i++) {
var arg = arguments[i];
if (_.isFunction(arg)) cb = arg;
if (_.isObject(arg)) opts = arg;
}
opts = _.extend({
title: _t('Question')
}, this.config, opts);
var titleOK = opts.yesNo ? _t('Yes') : _t('OK');
var titleCancel = opts.yesNo ? _t('No') : _t('Cancel');
element = this.box(str, {
title: opts.title,
onClose: function() {
if (doCall) cb(false);
},
buttons: [
{
text: titleCancel,
'class': 'btn',
click: function() {
cb(false);
doCall = false;
element.dialog('close');
}
},
{
text: titleOK,
'class': 'btn btn-primary',
click: function() {
cb(true);
doCall = false;
element.dialog('close');
}
}
]
});
return element;
},
box: function(str, options) {
var opts = $.extend({}, options);
var title = opts.title || _t('Information');
var onClose = opts.onClose || $.noop;
var onOpen = opts.onOpen || $.noop;
var buttons = opts.buttons || [
{
'text' : _t('OK'),
'class' : 'btn btn-primary',
'click' : function() {
element.dialog('close');
}
}
];
var element = $('<div class="message-box" style="padding: 15px;"></div>').attr('title', title).html(str);
var dialog = element.dialog({
dialogClass: 'ui-dialog-responsive ui-dialog-small ui-dialog-dragged',
resizable: false,
draggable: true,
autoOpen: false,
closeOnEscape: true,
modal: true,
zIndex: 1100,
open: function(e) {
onOpen(e);
},
close: function(e) {
onClose(e);
element.dialog('destroy');
element.remove();
},
show: {
effect: 'fade',
duration: 300
},
buttons: buttons
});
dialog.dialog('open');
return dialog;
}
};
// patch ui.dialog to maintain overlay opacity
['open', 'close', 'moveToTop'].forEach(function (name) {
var func = $.ui.dialog.prototype[name];
$.ui.dialog.prototype[name] = function () {
func.apply(this, arguments);
var all = $('body > .ui-widget-overlay').css('opacity', 0);
var last = name === 'close' ? all.last() : $(this.overlay.$el);
last.css('opacity', 0.3);
};
});
var elemNotifyStack = null;
var elemNotifyText = '<div class="alert alert-block fade in">'+
' <button type="button" class="close" data-dismiss="alert">×</button>'+
' <h4 class="alert-heading">#title#</h4>'+
' <p>#message#</p>'+
'</div>';
var elemNotifyText2 = '<div class="alert alert-block fade in">'+
' <button type="button" class="close" data-dismiss="alert">×</button>'+
' <strong>#title#</strong> #message#'+
'</div>';
function doNotify(message, options) {
if (elemNotifyStack === null) {
elemNotifyStack = $('<div class="notify-stack"></div>')
.css('position', 'fixed')
.css('bottom', 0)
.css('right', 10)
.zIndex(9999999)
.appendTo("body");
}
var opts = _.extend({
timeout: 5000
}, options);
var tmpl, elem;
tmpl = opts.alt ? elemNotifyText2 : elemNotifyText;
tmpl = tmpl.replace("#title#", opts.title || '').replace("#message#", message);
tmpl = axelor.sanitize(tmpl);
elem = $(tmpl)
.css('margin-bottom', 7)
.appendTo(elemNotifyStack);
if (opts.css) {
elem.addClass(opts.css);
}
_.delay(function () {
if (elem) {
elem.alert("close");
elem = null;
}
}, opts.timeout);
elem.alert();
}
var notify = {
info: function(message, options) {
var opts = _.extend({
title: _t('Information'),
css: 'alert-info'
}, options);
return doNotify(message, opts);
},
alert: function(message, options) {
var opts = _.extend({
title: _t('Alert')
}, options);
return doNotify(message, opts);
},
success: function(message, options) {
var opts = _.extend({
title: _t('Success'),
css: 'alert-primary'
}, options);
return doNotify(message, opts);
},
error: function(message, options) {
var opts = _.extend({
title: _t('Error'),
css: 'alert-error'
}, options);
return doNotify(message, opts);
}
};
this.axelor = this.axelor || {};
this.axelor.dialogs = dialogs;
this.axelor.notify = notify;
}).call(this);

38
sophal/js/lib/i18n.js Normal file
View File

@ -0,0 +1,38 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var bundle = (window._t || {}).bundle || {};
function gettext(key) {
var message = bundle[key] || bundle[(key||'').trim()] || key;
if (message && arguments.length > 1) {
for(var i = 1 ; i < arguments.length ; i++) {
var placeholder = new RegExp('\\{' + (i-1) + '\\}', 'g');
var value = arguments[i];
message = message.replace(placeholder, value);
}
}
return axelor.sanitize(message);
}
this._t = gettext;
}).call(this);

209
sophal/js/lib/tabs.js Normal file
View File

@ -0,0 +1,209 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var BSTabs = function(element) {
this.element = $(element);
this._setup();
};
BSTabs.prototype = {
constructor: BSTabs,
_setup: function() {
this.$elemStrip = this.element.find('.nav-tabs-strip:first');
this.$elemLeftScroller = this.element.find('.nav-tabs-scroll-l:first');
this.$elemRightScroller = this.element.find('.nav-tabs-scroll-r:first');
this.$elemMenu = this.element.find('.nav-tabs-menu:first');
this.$elemTabs = this.element.find('.nav-tabs:first').addClass('nav-tabs-scrollable');
if (this.$elemMenu.length) {
this.$elemRightScroller.css('right', 16);
}
var self = this;
this.$elemLeftScroller.click(function(){
self._scrollLeft();
return false;
});
this.$elemRightScroller.click(function(){
self._scrollRight();
return false;
});
var _onResize = _.debounce(function () { self._adjustScroll(); }, 300);
var _onAdjustSize = _.debounce(function () { self._adjustScroll(); });
var _onAdjust = function(event) {
event.stopPropagation();
setTimeout(function (){
self._adjustScroll();
});
};
$(window).on('resize', _onResize);
$(document).on('adjust:size', _onAdjustSize);
this.element.on('adjust:tabs', _onAdjust);
this.$elemTabs.on("click", " > li > a", function(event){
self._adjustTab($(this).parent(), true);
});
this.element.on('$destroy', function () {
$(window).off('resize', _onResize);
$(document).off('adjust:size', _onAdjustSize);
});
},
_getTabsWidth: function() {
var widthTabs = 0;
this.$elemTabs.find('> li:visible').each(function(){
widthTabs += $(this).outerWidth(true);
});
return widthTabs;
},
_scrollLeft: function() {
if (this.$elemLeftScroller.hasClass('disabled'))
return;
var x = this.$elemTabs.position().left;
var scrollTo = Math.min(0, x + 100);
this._scrollTabs(scrollTo, true);
},
_scrollRight: function() {
if (this.$elemRightScroller.hasClass('disabled'))
return;
var x = this.$elemTabs.position().left;
var w = this._getTabsWidth();
var mx = - (w - this.$elemStrip.width());
var scrollTo = Math.max(mx, x - 100);
this._scrollTabs(scrollTo, true);
},
_scrollTabs: function(scrollTo, animate) {
if (scrollTo === this._lastScrollTo) {
return;
}
this._lastScrollTo = scrollTo;
if (animate) {
var self = this;
return this.$elemTabs.animate({
'left': scrollTo
}, 300, function() {
self._activateScrollers();
});
}
this.$elemTabs.css('left', scrollTo);
this._activateScrollers();
},
_activateScrollers: function() {
if (this.$elemTabs.position().left < 0) {
this.$elemLeftScroller.removeClass('disabled');
} else {
this.$elemLeftScroller.addClass('disabled');
}
if (this._getTabsWidth() + this.$elemTabs.position().left > this.$elemStrip.width() + 1) {
this.$elemRightScroller.removeClass('disabled');
} else {
this.$elemRightScroller.addClass('disabled');
}
},
_adjustTab: function(tab, animate) {
if (!$(tab).length) return;
var w = this.$elemStrip.innerWidth();
var scrollTo = this.$elemTabs.position().left;
var left = $(tab).position().left + scrollTo;
var right = left + $(tab).outerWidth(true);
if (left < 0) {
scrollTo -= left;
} else if (right > w){
scrollTo -= right - w;
}
this._scrollTabs(scrollTo, animate);
},
_adjustScroll: function() {
var widthStrip = this.$elemStrip.width();
var widthTabs = this._getTabsWidth();
this.element.toggleClass("nav-tabs-overflow", widthStrip < widthTabs);
var scrollTo = 0;
if (widthStrip >= widthTabs) {
this.$elemLeftScroller.hide();
this.$elemRightScroller.hide();
this.$elemMenu.hide();
this.$elemStrip.css('margin', '0');
} else {
this.$elemLeftScroller.show();
this.$elemRightScroller.show();
this.$elemMenu.show();
this.$elemStrip.css('margin', this.$elemMenu.length ? '0 32px 0 16px' : '0 16px');
var left = this.$elemTabs.position().left;
var right = widthTabs + left;
if (right < widthStrip) {
scrollTo = left + (widthStrip - right);
} else {
var tab = this.$elemTabs.find('> li.active');
if (tab) {
return this._adjustTab(tab);
}
}
}
this._scrollTabs(scrollTo);
}
};
$.fn.bsTabs = function () {
return this.each(function () {
var $this = $(this),
data = $this.data('bsTabs');
if (!data) $this.data('bsTabs', (data = new BSTabs(this)));
});
};
$.fn.bsTabs.Constructor = BSTabs;
})();

95
sophal/js/lib/utils.js Normal file
View File

@ -0,0 +1,95 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
// integrate underscore.string with underscore
_.mixin(_.str.exports());
var util = this.util ? this.util : this.util = {};
/**
* Based on the `util.inherits` of `Node.js` with some additional features
*
* 1. The super prototype is attached to the base prototype as `super_`.
* 2. The base prototype can be provided as third argument.
*
* Example:
*
* function Hello(message) {
* this.message = message;
* }
*
* Hello.prototype.say = function(what) {
* console.log(what || this.message);
* }
*
* function HelloWorld(message) {
* this.super_.constructor.apply(this, arguments);
* }
*
* util.inherits(HelloWorld, Hello, {
*
* say: function(what) {
* // do something
* this.super_.say.apply(this, arguments);
* }
* });
*
* @param ctor the base constructor
* @param superCtor the super constructor
* @param proto the prototype of the base constructor (optional)
*
* @returns the base constructor
*/
util.inherits = function(ctor, superCtor, /* optional */ proto) {
var props = {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
};
Object.getOwnPropertyNames(proto||{}).forEach(function(name) {
props[name] = Object.getOwnPropertyDescriptor(proto, name);
});
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, props);
ctor.prototype.super_ = superCtor.prototype;
return ctor;
};
/**
* Shortcut to `util.inherits`.
*
* @param superCtor the super constructor
* @param proto the prototype for this contructor
*
* @returns this constructor itself
*/
Function.prototype.inherits = function(superCtor, /* optional */ proto) {
return util.inherits(this, superCtor, proto);
};
}).call(this);

867
sophal/js/view/view.base.js Normal file
View File

@ -0,0 +1,867 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function () {
"use strict";
var ui = angular.module("axelor.ui");
ui.ViewCtrl = ViewCtrl;
ui.ViewCtrl.$inject = ['$scope', 'DataSource', 'ViewService'];
function ViewCtrl($scope, DataSource, ViewService) {
$scope._viewParams = $scope._viewParams || $scope.selectedTab;
if (!$scope._viewParams) {
throw "View parameters are not provided.";
}
var params = $scope._viewParams;
$scope._views = ViewService.accept(params);
$scope._viewType = params.viewType;
if ($scope.$parent && $scope.$parent._model) {
$scope._parentModel = $scope.$parent._model;
}
$scope._model = params.model;
$scope._fields = {};
$scope._dataSource = null;
$scope._domain = params.domain;
$scope._context = params.context;
if (params.model) {
$scope._dataSource = DataSource.create(params.model, params);
}
$scope._defer = function() {
return ViewService.defer();
};
$scope.loadView = function(viewType, viewName) {
var view = $scope._views[viewType] || {
type: viewType,
name: viewName
};
var ctx = $scope._context;
if ($scope.getContext) {
ctx = $scope.getContext();
}
return ViewService.getMetaDef($scope._model, view, ctx);
};
$scope.loadFields = function() {
return ViewService.getFields($scope._model);
};
$scope.updateRoute = function() {
this.$emit("on:update-route");
};
$scope.getRouteOptions = function() {
throw "Not Implemented.";
};
$scope.setRouteOptions = function(options) {
throw "Not Implemented.";
};
var switchedTo = null;
$scope.switchTo = function(viewType, /* optional */ callback) {
var view = $scope._views[viewType];
if (!view) {
return;
}
var promise = view.deferred.promise;
promise.then(function(viewScope){
if (!viewScope || switchedTo === viewType) {
return;
}
switchedTo = viewType;
$scope._viewTypeLast = $scope._viewType === 'form' ? $scope._viewTypeLast : $scope._viewType;
$scope._viewType = viewType;
$scope._viewParams.viewType = viewType; //XXX: remove
$scope._viewParams.$viewScope = viewScope;
viewScope.show();
if (viewScope.updateRoute) {
viewScope.updateRoute();
}
if (callback) {
callback(viewScope);
}
});
};
if (!params.action) {
return;
}
// hide toolbar button titles
$scope.tbTitleHide = !axelor.config['view.toolbar.titles'];
function switchAndEdit(id, readonly) {
$scope.switchTo('form', function(scope) {
scope._viewPromise.then(function() {
scope.doRead(id).success(function(record) {
scope.edit(record);
scope.setEditable(!readonly);
});
});
});
}
// show single or default record if specified
var context = params.context || {};
if (context._showSingle || context._showRecord) {
var ds = $scope._dataSource;
var forceEdit = (params.params||{}).forceEdit === true;
if (context._showRecord > 0) {
params.viewType = "form";
return $scope.switchTo('form');
}
return ds.search({
offset: 0,
limit: 2,
fields: ["id"]
}).success(function(records, page){
if (page.total === 1 && records.length === 1) {
return switchAndEdit(records[0].id, !forceEdit);
}
return $scope.switchTo($scope._viewType || 'grid');
});
}
// switch to the the current viewType
$scope.switchTo($scope._viewType || 'grid');
}
/**
* Base controller for DataSource views. This controller should not be used
* directly but actual controller should inherit from it.
*
*/
ui.DSViewCtrl = function DSViewCtrl(type, $scope, $element) {
if (!type) {
throw "No view type provided.";
}
if (!$scope._dataSource) {
throw "DataSource is not provided.";
}
$scope._viewResolver = $scope._defer();
$scope._viewPromise = $scope._viewResolver.promise;
var ds = $scope._dataSource;
var view = $scope._views[type] || {};
var viewPromise = null;
var hiddenButtons = {};
var params = $scope._viewParams;
if (params.params && params.params.limit) {
if (ds && ds._page) {
ds._page.limit = +(params.params.limit) || ds._page.limit;
}
}
$scope.fields = {};
$scope.fields_related = {};
$scope.schema = null;
$scope.show = function() {
if (!viewPromise) {
viewPromise = $scope.loadView(type, view.name);
viewPromise.then(function(meta){
var schema = meta.view;
var fields = meta.fields || params.fields;
var toolbar = [];
_.each(schema.toolbar, function(button){
button.custom = true;
if (/^(new|edit|save|delete|copy|cancel|back|refresh|search|export|log|files)$/.test(button.name)) {
hiddenButtons[button.name] = button;
button.custom = false;
}
toolbar.push(button);
});
var forceTitle = params.forceTitle;
if (forceTitle === undefined) {
forceTitle = (params.params||{}).forceTitle;
}
if (!forceTitle && schema.title) {
$scope.viewTitle = schema.title;
}
$scope.fields = fields;
$scope.fields_related = meta.related;
$scope.schema = schema;
$scope.toolbar = toolbar;
$scope.menubar = schema.menubar;
$scope.toolbarAsMenu = _.isEmpty(toolbar) ? null : [{
icon: 'fa-wrench',
isButton: true,
items: _.map(toolbar, function (item) {
return _.extend({}, item, {
name: item.name,
action: item.onClick,
title: item.title || item.autoTitle || item.name
});
})
}];
// watch on view.loaded to improve performance
schema.loaded = true;
});
}
$scope.onShow(viewPromise);
};
$scope.onShow = function(promise) {
};
$scope.canNext = function() {
return ds && ds.canNext();
};
$scope.canPrev = function() {
return ds && ds.canPrev();
};
$scope.getPageSize = function() {
var page = ds && ds._page;
if (page) {
return page.limit;
}
return 40;
};
$scope.setPageSize = function(value) {
var page = ds && ds._page,
limit = Math.max(0, +value) || 40;
if (page && page.limit != limit) {
page.limit = limit;
$scope.onRefresh();
}
};
var can = (function (scope) {
var fn = null;
var perms = {
'new': 'create',
'copy': 'create',
'edit': 'write',
'save': 'write',
'delete': 'remove',
'archive': 'remove',
'export': 'export'
};
var actions = {
'new': 'canNew',
'edit': 'canEdit',
'save': 'canSave',
'copy': 'canCopy',
'delete': 'canDelete',
'archive': 'canArchive',
'attach': 'canAttach'
};
function attr(which) {
if (fn === null && _.isFunction(scope.attr)) {
fn = scope.attr;
}
return !fn || fn(which) !== false;
}
function perm(which) {
return which === undefined || scope.hasPermission(which);
}
return function can(what) {
return attr(actions[what]) && perm(perms[what]);
};
})($scope);
$scope.hasButton = function(name) {
if (!can(name)) {
return false;
}
if (_(hiddenButtons).has(name)) {
var button = hiddenButtons[name];
if (button.isHidden) {
return !button.isHidden();
}
return !button.hidden;
}
return true;
};
$scope.hasPermission = function(perm) {
var view = $scope.schema;
var defaultValue = arguments.length === 2 ? arguments[1] : true;
if (!view || !view.perms) return defaultValue;
var perms = view.perms;
var permitted = perms[perm];
if (permitted === undefined) {
return defaultValue;
}
return _.toBoolean(permitted);
};
$scope.isPermitted = function(perm, record, callback) {
var ds = this._dataSource;
ds.isPermitted(perm, record).success(function(res){
var errors = res.errors;
if (errors) {
return axelor.dialogs.error(errors.read);
}
callback();
});
};
$scope.canShowToolbar = function() {
var params = ($scope._viewParams || {}).params;
if (params && params['show-toolbar'] === false) {
return false;
}
return true;
};
$scope.hasHelp = function() {
var view = $scope.schema;
return view ? view.helpLink : false;
};
$scope.onShowHelp = function() {
if ($scope.hasHelp()) {
window.open($scope.schema.helpLink);
}
};
if (view.deferred) {
view.deferred.resolve($scope);
}
$scope.$on('on:tab-reload', function(e, tab) {
if ($scope === e.targetScope && $scope.onRefresh) {
$scope.onRefresh();
}
});
};
ui.directive('uiViewPane', function() {
return {
replace: true,
controller: ['$scope', '$attrs', 'DataSource', 'ViewService', function ($scope, $attrs, DataSource, ViewService) {
var params = $scope.$eval($attrs.uiViewPane);
$scope._viewParams = params;
ViewCtrl.call(this, $scope, DataSource, ViewService);
$scope.viewList = [];
$scope.viewType = null;
var switchTo = $scope.switchTo;
$scope.switchTo = function (type, callback) {
var view = $scope._views[type];
if (view && $scope.viewList.indexOf(type) === -1) {
$scope.viewList.push(type);
}
var viewScope = !$scope._isPopup && $scope.selectedTab && $scope.selectedTab.$viewScope;
if (viewScope && viewScope.viewType === 'form' && viewScope.viewType !== type) {
viewScope.$$resetForm();
}
$scope.viewType = type;
return switchTo(type, callback);
};
$scope.$watch('selectedTab.viewType', function viewTypeWatch(type) {
var params = $scope._viewParams;
if (params && params.$viewScope !== ($scope.selectedTab || {}).$viewScope) {
return;
}
if ($scope.viewType !== type && type) {
$scope.switchTo(type);
}
});
$scope.viewTemplate = function (type) {
var tname = "ui-template:" + type;
var template = type;
if (params.params && params.params[tname]) {
template = params.params[tname];
}
return 'partials/views/' + template + '.html';
};
var type = params.viewType || params.type;
$scope.keepAttached = $scope._isPopup || (params.params||{}).popup || type === 'html';
$scope.switchTo(type);
}],
link: function(scope, element, attrs) {
},
template:
"<div class='view-pane' ui-attach='keepAttached || tab.selected'>" +
"<div class='view-container'" +
" ng-repeat='type in viewList'" +
" ui-show='type === viewType'" +
" ui-attach-scroll ui-attach='keepAttached || type == viewType'" +
" ng-include='viewTemplate(type)'></div>" +
"</div>"
};
});
ui.directive('uiViewPopup', function() {
return {
controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
var params = $scope.$eval($attrs.uiViewPopup);
$scope.tab = params;
$scope._isPopup = true;
$scope.onHotKey = function (e, action) {
return false;
};
var canClose = false;
$scope.onOK = function () {
$scope.closeTab($scope.tab, function() {
canClose = true;
$element.dialog('close');
});
};
$scope.onBeforeClose = function(e) {
if (canClose) {
return;
}
e.preventDefault();
e.stopPropagation();
$scope.onOK();
};
$scope.onPopupClose = function () {
var tab = $scope.tab,
params = tab.params || {},
parent = tab.$popupParent;
while (parent && parent.$$destroyed && parent.tab) {
parent = parent.tab.$popupParent;
}
if (parent && parent.reload && params.popup === "reload") {
parent.reload();
}
$scope.$applyAsync();
};
$scope.onPopupOK = function () {
var viewScope = $scope._viewParams.$viewScope;
if (!viewScope.onSave || (!viewScope.isDirty() && viewScope.id)) {
return $scope.onOK();
}
return viewScope.onSave({ fireOnLoad: false }).then(function(record, page) {
viewScope.edit(record);
viewScope.$timeout($scope.onOK.bind($scope));
});
};
params = $scope.tab.params || {};
if (params['popup-save'] === false) {
$scope.onPopupOK = false;
}
}],
link: function (scope, element, attrs) {
scope.$watch('viewTitle', function viewTitleWatch(title) {
scope._setTitle(title);
});
scope.waitForActions(function () {
if (scope._viewParams.viewType === 'html') {
scope.viewTitle = scope.tabTitle(scope._viewParams);
scope._doShow();
return;
}
var unwatch = scope.$watch("_viewParams.$viewScope.schema.loaded", function viewLoadedWatch(loaded) {
if (!loaded) {
return;
}
unwatch();
var viewScope = scope._viewParams.$viewScope;
var viewPromise = viewScope._viewPromise;
scope.viewTitle = scope.tabTitle(scope._viewParams);
scope.$broadcast('grid:adjust-size', viewScope);
scope._doShow(viewPromise);
});
});
},
replace: true,
template:
'<div ui-dialog ui-dialog-size x-resizable="true" x-on-close="onPopupClose" x-on-ok="onPopupOK" x-on-before-close="onBeforeClose">' +
'<div ui-view-pane="tab"></div>' +
'</div>'
};
});
ui.directive('uiRecordPager', function(){
return {
replace: true,
link: function(scope, element, attrs) {
var elText = element.find('.record-pager-text').show(),
elChanger = element.find('.record-pager-change').hide(),
elInput = elChanger.find('input');
scope.showText = attrs.uiRecordPager !== "no-text";
function updatePageSize() {
var size = +(elInput.val()) || 0;
if (scope.setPageSize && size > 0) {
scope.setPageSize(size);
}
elText.add(elChanger).toggle();
}
elText.click(function(e) {
elText.add(elChanger).toggle();
elInput.zIndex(elInput.parent().zIndex() + 1).focus().select();
});
elInput.on('click', function () {
elInput.zIndex(elInput.parent().zIndex() + 1).focus().select();
});
elChanger.on('click', 'button', function() {
updatePageSize();
});
elChanger.keyup(function(e) {
if(e.keyCode == 13) { // ENTER
updatePageSize();
}
});
},
template:
'<div class="record-pager hidden-phone">'+
'<span ng-show="showText">'+
'<span class="record-pager-text">{{pagerText()}}</span>'+
'<span class="input-append record-pager-change">'+
'<input type="text" style="width: 30px;" value="{{getPageSize()}}">'+
'<button type="button" class="btn add-on"><i class="fa fa-check"></i></button>'+
'</span>'+
'</span>'+
'<div class="btn-group">'+
'<button class="btn" ng-disabled="!canPrev()" ng-click="onPrev()"><i class="fa fa-chevron-left"></i></button>'+
'<button class="btn" ng-disabled="!canNext()" ng-click="onNext()"><i class="fa fa-chevron-right"></i></button>'+
'</div>'+
'</div>'
};
});
ui.directive('uiViewCustomize', ['NavService', function(NavService) {
return {
scope: true,
link: function (scope, element, attrs) {
scope.canShow = function () {
if (!axelor.config['user.technical']) {
return false;
}
var viewScope = (scope.selectedTab || {}).$viewScope;
var view = viewScope && viewScope.schema;
return view && (view.viewId || view.modelId);
};
scope.hasViewID = function () {
var viewScope = (scope.selectedTab || {}).$viewScope;
var view = viewScope && viewScope.schema;
return view && view.viewId;
};
scope.hasModelID = function () {
var viewScope = (scope.selectedTab || {}).$viewScope;
var view = viewScope && viewScope.schema;
return view && view.modelId;
};
scope.hasActionID = function () {
return (scope.selectedTab || {}).actionId;
};
scope.onShowView = function () {
var id = scope.hasViewID();
NavService.openTabByName("form::com.axelor.meta.db.MetaView", {
mode: "edit",
state: id
});
scope.waitForActions(function () {
var vs = (scope.selectedTab || {}).$viewScope;
if (vs && vs.setEditable) {
vs.setEditable();
}
});
};
scope.onShowModel = function () {
var id = scope.hasModelID();
NavService.openTabByName("form::com.axelor.meta.db.MetaModel", {
mode: "edit",
state: id
});
};
scope.onShowAction = function () {
var id = scope.hasActionID();
NavService.openTabByName("form::com.axelor.meta.db.MetaAction", {
mode: "edit",
state: id
});
};
},
replace: true,
template:
"<ul ng-show='canShow()' class='nav menu-bar view-customize hidden-phone'>" +
"<li class='dropdown menu'>" +
"<a class='dropdown-toggle btn' data-toggle='dropdown' title='{{ \"Customize...\" | t}}'>" +
"<i class='fa fa-wrench'></i>" +
"</a>" +
"<ul class='dropdown-menu pull-right'>" +
"<li><a ng-click='onShowView()' ng-show='hasViewID()'>View...</a></li>" +
"<li><a ng-click='onShowModel()' ng-show='hasModelID()'>Model...</a></li>" +
"<li><a ng-click='onShowAction()' ng-show='hasActionID()'>Action...</a></li>" +
"</ul>" +
"</li>" +
"</ul>"
};
}]);
function viewSwitcher(scope, element, attrs) {
var params = (scope._viewParams || scope.tab);
var viewTypes = _.pluck(params.views, 'type');
if ((params.viewType || params.type) === 'dashboard') {
element.hide();
return;
}
element.find("[x-view-type]").click(function(e) {
if (this.disabled) {
return;
}
var type = $(this).attr("x-view-type");
var vs = params.$viewScope || (scope.selectedTab || {}).$viewScope;
var ds = vs._dataSource;
var page = ds && ds._page;
if (type === "form" && page) {
if (page.index === -1) page.index = 0;
}
if ((scope.selectedTab || {}).viewType === 'grid') {
var items = vs.getItems() || [];
var index = _.first(vs.selection || []);
if (index === undefined && items.length === 0 && vs.schema.canNew === false) {
return;
}
if (index !== undefined) page.index = index;
}
vs.switchTo(type);
vs.$applyAsync();
}).each(function() {
var type = $(this).attr("x-view-type");
if (viewTypes.indexOf(type) === -1) {
$(this).hide();
}
});
var watchExpr = scope._viewParams ? '_viewType' : 'tab.viewType';
scope.$watch(watchExpr, function viewTypeWatch(type) {
element.find("[x-view-type]").attr("disabled", false);
element.find("[x-view-type][x-view-type=" + type + "]").attr("disabled", true);
});
}
ui.directive('uiViewSwitcher', function(){
return {
scope: true,
link: function(scope, element, attrs) {
element.parents('.view-container:first').addClass('has-toolbar');
viewSwitcher(scope, element, attrs);
},
replace: true,
template:
'<div class="view-switcher pull-right hidden-phone">'+
'<div class="btn-group">'+
'<button class="btn" x-view-type="grid"><i class="fa fa-list"></i></button>'+
'<button class="btn" x-view-type="cards"><i class="fa fa-th-large"></i></button>'+
'<button class="btn" x-view-type="kanban"><i class="fa fa-columns"></i></button>'+
'<button class="btn" x-view-type="calendar"><i class="fa fa-calendar"></i></button>'+
'<button class="btn" x-view-type="gantt"><i class="fa fa-calendar"></i></button>'+
'<button class="btn" x-view-type="chart"><i class="fa fa-bar-chart-o"></i></button>'+
'<button class="btn" x-view-type="form" ><i class="fa fa-file-text-o"></i></button>'+
'</div>'+
'</div>'
};
});
ui.directive('uiViewSwitcherMenu', function(){
return {
scope: true,
link: function(scope, element, attrs) {
viewSwitcher(scope, element, attrs);
},
replace: true,
template:
"<span class='view-switch-menu dropdown pull-right'>" +
"<a href='' class='dropdown-toggle' data-toggle='dropdown'><i class='fa fa-ellipsis-v'></i></a>" +
"<ul class='dropdown-menu'>" +
"<li><a href='' x-view-type='grid' x-translate>Grid</a></li>" +
"<li><a href='' x-view-type='cards' x-translate>Cards</a></li>" +
"<li><a href='' x-view-type='kanban' x-translate>Kanban</a></li>" +
"<li><a href='' x-view-type='calendar' x-translate>Calendar</a></li>" +
"<li><a href='' x-view-type='gantt' x-translate>Gantt</a></li>" +
"<li><a href='' x-view-type='chart' x-translate>Chart</a></li>" +
"<li><a href='' x-view-type='form' x-translate>Form</a></li>" +
"</ul>" +
"</span>"
};
});
ui.directive('uiHotKeys', function() {
var keys = {
45: 'new', // insert
69: 'edit', // e
83: 'save', // s
68: 'delete', // d
82: 'refresh', // r
70: 'search', // f
71: 'select', // g
74: 'prev', // j
75: 'next', // n
77: 'focus-menu', // m
120: 'toggle-menu', // F9
81: 'close' // q
};
return function(scope, element, attrs) {
var loginWindow = $("#loginWindow");
$(document).on('keydown.axelor-keys', function (e) {
if (loginWindow.is(":visible")) {
return;
}
// disable backspace as back button
if (e.which === 8 && e.target === document.body) {
e.preventDefault();
return false;
}
var action = keys[e.which];
if (action === "toggle-menu") {
$('#offcanvas-toggle a').click();
return false;
}
if (e.altKey || e.shiftKey || !e.ctrlKey) {
return;
}
if (action === "focus-menu") {
var activeMenu = $('.sidebar .nav-tree li.active');
if (activeMenu.length === 0) {
activeMenu = $('.sidebar .nav-tree li:first');
}
var navTree = activeMenu.parents('[nav-tree]:first');
if (navTree.length) {
navTree.navtree('selectItem', activeMenu);
}
return false;
}
var tab = scope.selectedTab,
dlg = $('[ui-editor-popup]:visible:last,[ui-view-popup]:visible:last,[ui-dms-popup]:visible:last').first(),
vs = tab ? tab.$viewScope : null;
if (dlg.length) {
vs = dlg.scope();
}
if (!vs || !keys.hasOwnProperty(e.which)) {
return;
}
if (action === "close") {
scope.closeTab(tab, function() {
scope.$applyAsync();
});
return false;
}
if (action === "search") {
var filterBox = $('.filter-box .search-query:visible');
if (filterBox.length) {
filterBox.focus().select();
return false;
}
}
if (_.isFunction(vs.onHotKey)) {
return vs.onHotKey(e, action);
}
});
scope.$on('$destroy', function() {
$(document).off('keydown.axelor-keys');
});
};
});
})();

View File

@ -0,0 +1,769 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/* global d3: true */
"use strict";
var ui = angular.module('axelor.ui');
ui.controller('CalendarViewCtrl', CalendarViewCtrl);
CalendarViewCtrl.$inject = ['$scope', '$element'];
function CalendarViewCtrl($scope, $element) {
ui.DSViewCtrl('calendar', $scope, $element);
var ds = $scope._dataSource;
var view = {};
var colors = {};
var initialized = false;
$scope.onShow = function(viewPromise) {
if (initialized) {
return $scope.refresh();
}
viewPromise.then(function(){
var schema = $scope.schema;
initialized = true;
view = {
start: schema.eventStart,
stop: schema.eventStop,
length: parseInt(schema.eventLength) || 0,
color: schema.colorBy,
title: schema.items[0].name
};
$scope._viewResolver.resolve(schema, $element);
$scope.updateRoute();
});
};
$scope.isAgenda = function() {
var field = this.fields[view.start];
return field && field.type === "datetime";
};
var d3_colors = d3.scale.category10().range().concat(
d3.scale.category20().range());
function nextColor(n) {
if (n === undefined || n < 0 || n >= d3_colors.length) {
n = _.random(0, d3_colors.length);
}
var c = d3.rgb(d3_colors[n]);
return {
bg: "" + c,
fg: "" + c.brighter(99),
bc: "" + c.darker(0.9)
};
}
$scope.fetchItems = function(start, end, callback) {
var fields = _.pluck(this.fields, 'name');
var criteria = {
operator: "and",
criteria: [{
fieldName: view.start,
operator: ">=",
value: start
}, {
fieldName: view.start,
operator: "<=",
value: end
}]
};
// make sure to include items whose end date falls in current range
if (view.stop) {
criteria = {
operator: "or",
criteria: [criteria, {
operator: "and",
criteria: [{
fieldName: view.stop,
operator: ">=",
value: start
}, {
fieldName: view.stop,
operator: "<=",
value: end
}]
}]
};
}
// consider stored filter
if (ds._filter) {
if (ds._filter.criteria) {
criteria = {
operator: "and",
criteria: [criteria].concat(ds._filter.criteria)
};
}
if (_.size(ds._filter._domains) > 0) {
criteria._domains = ds._filter._domains;
}
}
var opts = {
fields: fields,
filter: criteria,
domain: this._domain,
context: this._context,
store: false,
limit: -1
};
ds.search(opts).success(function(records) {
var items = _.clone(records);
items.sort(function (x, y) { return x.id - y.id; });
updateColors(items, true);
callback(records);
});
};
function updateColors(records, reset) {
var colorBy = view.color;
var colorField = $scope.fields[colorBy];
if (!colorField) {
return colors;
}
if (reset) {
colors = {};
}
_.each(records, function(record) {
var item = record[colorBy];
if (item === null || item === undefined) {
return;
}
var key = $scope.getColorKey(record, item);
var title = colorField.targetName ? item[colorField.targetName] : item;
if (colorField.selectionList) {
var select = _.find(colorField.selectionList, function (select) {
return ("" + select.value) === ("" + title);
});
if (select) {
title = select.title;
}
}
if (!colors[key]) {
colors[key] = {
item: item,
title: title || _t('Unknown'),
color: nextColor(_.size(colors))
};
}
record.$colorKey = key;
});
return colors;
}
$scope.getColors = function() {
return colors;
};
$scope.getColor = function(record) {
var key = this.getColorKey(record);
if (key && !colors[key]) {
updateColors([record], false);
}
if (colors[key]) {
return colors[key].color;
}
return nextColor(0);
};
$scope.getColorKey = function(record, key) {
if (key) {
return "" + (key.id || key);
}
if (record) {
return this.getColorKey(null, record[view.color]);
}
return null;
};
$scope.getEventInfo = function(record) {
var info = {},
value;
value = record[view.start];
info.start = value ? moment(value) : moment();
value = record[view.stop];
info.end = value ? moment(value) : moment(info.start).add(view.length || 1, "hours");
var title = this.fields[view.title];
var titleText = null;
if (title) {
value = record[title.name];
if (title.targetName) {
value = value[title.targetName];
} else if (title.selectionList) {
var select = _.find(title.selectionList, function (select) {
return ("" + select.value) === ("" + value);
});
if (select) {
titleText = select.title;
}
}
titleText = titleText || value || _t('Unknown');
}
info.title = ("" + titleText);
info.allDay = isAllDay(info);
info.className = info.allDay ? "calendar-event-allDay" : "calendar-event-day";
return info;
};
function isAllDay(event) {
if($scope.fields[view.start] && $scope.fields[view.start].type === 'date') {
return true;
}
var start = moment(event.start);
var end = moment(event.end);
if (start.format("HH:mm") !== "00:00") {
return false;
}
return !event.end || end.format("HH:mm") === "00:00";
}
$scope.onEventChange = function(event, delta) {
var record = _.clone(event.record);
var start = event.start;
var end = event.end;
if (isAllDay(event)) {
start = start.clone().startOf("day").local();
end = (end || start).clone().startOf("day").local();
}
record[view.start] = start;
record[view.stop] = end;
$scope.record = record;
function reset() {
$scope.record = null;
}
function doSave() {
return ds.save(record).success(function(res){
return $scope.refresh();
});
}
var handler = $scope.onChangeHandler;
if (handler) {
var promise = handler.onChange().then(function () {
return doSave();
});
promise.success = function(fn) {
promise.then(fn);
return promise;
};
promise.error = function (fn) {
promise.then(null, fn);
return promise;
};
return promise;
}
return doSave();
};
$scope.removeEvent = function(event, callback) {
ds.remove(event.record).success(callback);
};
$scope.select = function() {
};
$scope.refresh = function() {
};
$scope.getRouteOptions = function() {
var args = [],
query = {};
return {
mode: 'calendar',
args: args,
query: query
};
};
$scope.setRouteOptions = function(options) {
var opts = options || {};
if (opts.mode === "calendar") {
return $scope.updateRoute();
}
var params = $scope._viewParams;
if (params.viewType !== "calendar") {
return $scope.show();
}
};
$scope.canNext = function() {
return true;
};
$scope.canPrev = function() {
return true;
};
}
angular.module('axelor.ui').directive('uiViewCalendar', ['ViewService', 'ActionService', function(ViewService, ActionService) {
function link(scope, element, attrs, controller) {
var main = element.children('.calendar-main');
var mini = element.find('.calendar-mini');
var legend = element.find('.calendar-legend');
var ctx = (scope._viewParams.context||{});
var params = (scope._viewParams.params||{});
var schema = scope.schema;
var mode = ctx.calendarMode || params.calendarMode || schema.mode || "month";
var date = ctx.calendarDate || params.calendarDate;
var editable = schema.editable === undefined ? true : schema.editable;
var calRange = {};
var RecordManager = (function () {
var records = [],
current = [];
function add(record) {
if (!record || _.isArray(record)) {
records = record || [];
return filter();
}
var found = _.findWhere(records, {
id: record.id
});
if (!found) {
found = record;
records.push(record);
}
if (found !== record) {
_.extend(found, record);
}
return filter();
}
function remove(record) {
records = _.filter(records, function (item) {
return record.id !== item.id;
});
return filter();
}
function filter() {
var selected = [];
_.each(scope.getColors(), function (color) {
if (color.checked) {
selected.push(scope.getColorKey(null, color.item));
}
});
main.fullCalendar('removeEventSource', current);
current = records;
if (selected.length) {
current = _.filter(records, function(record) {
return _.contains(selected, record.$colorKey);
});
}
main.fullCalendar('addEventSource', current);
adjustSize();
}
function events(start, end, timezone, callback) {
calRange.start = start;
calRange.end = end;
scope._viewPromise.then(function(){
scope.fetchItems(start, end, function(items) {
callback([]);
add(items);
});
});
}
return {
add: add,
remove: remove,
filter: filter,
events: events
};
}());
if (date) {
date = moment(date).toDate();
}
mini.datepicker({
showOtherMonths: true,
selectOtherMonths: true,
onSelect: function(dateStr) {
main.fullCalendar('gotoDate', mini.datepicker('getDate'));
}
});
if (date) {
mini.datepicker('setDate', date);
}
var lang = axelor.config["user.lang"] || 'en';
var options = {
header: false,
timeFormat: 'h(:mm)t',
axisFormat: 'h(:mm)t',
timezone: 'local',
lang: lang,
editable: editable,
selectable: editable,
selectHelper: editable,
select: function(start, end) {
var event = {
start: start,
end: end
};
// all day
if (!start.hasTime() && !end.hasTime()) {
event.start = start.clone().startOf("day").local();
event.end = end.clone().startOf("day").local();
}
scope.$applyAsync(function(){
scope.showEditor(event);
});
main.fullCalendar('unselect');
},
defaultDate: date,
events: RecordManager,
eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
hideBubble();
scope.onEventChange(event, delta).error(function(){
revertFunc();
});
},
eventResize: function( event, delta, revertFunc, jsEvent, ui, view ) {
scope.onEventChange(event, delta).error(function(){
revertFunc();
});
},
eventClick: function(event, jsEvent, view) {
showBubble(event, jsEvent.target);
},
eventDataTransform: function(record) {
return updateEvent(null, record);
},
viewDisplay: function(view) {
hideBubble();
mini.datepicker('setDate', main.fullCalendar('getDate'));
},
allDayText: _t('All Day')
};
if (lang.indexOf('fr') === 0) {
_.extend(options, {
timeFormat: 'H:mm',
axisFormat: 'H:mm',
firstDay: 1,
views: {
week: {
titleFormat: 'D MMM YYYY',
columnFormat: 'ddd DD/MM'
},
day: {
titleFormat: 'D MMM YYYY'
}
}
});
}
main.fullCalendar(options);
var editor = null;
var bubble = null;
function hideBubble() {
if (bubble) {
bubble.popover('destroy');
bubble = null;
}
}
function showBubble(event, elem) {
hideBubble();
bubble = $(elem).popover({
html: true,
title: "<b>" + event.title + "</b>",
placement: "top",
container: 'body',
content: function() {
var html = $("<div></div>").addClass("calendar-bubble-content");
var start = event.start;
var end = event.end && event.allDay ? moment(event.end).add(-1, 'second') : event.end;
var singleDay = (event.allDay || !scope.isAgenda()) && (!end || moment(start).isSame(end, 'day'));
var dateFormat = !scope.isAgenda() || event.allDay ? "ddd D MMM" : "ddd D MMM HH:mm";
$("<span>").text(moment(start).format(dateFormat)).appendTo(html);
if (schema.eventStop && end && !singleDay) {
$("<span> - </span>").appendTo(html);
$("<span>").text(moment(end).format(dateFormat)).appendTo(html);
}
$("<hr>").appendTo(html);
if (scope.isEditable()) {
$('<a href="javascript: void(0)" style="margin-right: 5px;"></a>').text(_t("Delete"))
.appendTo(html)
.click(function(e){
hideBubble();
scope.$applyAsync(function(){
scope.removeEvent(event, function() {
RecordManager.remove(event.record);
});
});
});
}
$('<a class="pull-right" href="javascript: void(0)"></a>')
.append(_t("Edit event")).append("<strong> »</strong>")
.appendTo(html)
.click(function(e){
hideBubble();
scope.$applyAsync(function(){
scope.showEditor(event);
});
});
return html;
}
});
bubble.popover('show');
}
$("body").on("mousedown", function(e){
var elem = $(e.target || e.srcElement);
if (!bubble || bubble.is(elem) || bubble.has(elem).length) {
return;
}
if (!elem.parents().is(".popover")) {
hideBubble();
}
});
function updateEvent(event, record) {
if (!event || !event.id) {
var color = scope.getColor(record);
event = {
id: record.id,
record: record,
backgroundColor: color.bg,
borderColor: color.bc,
textColor: color.fg
};
} else {
_.extend(event.record, record);
}
event = _.extend(event, scope.getEventInfo(record));
return event;
}
scope.editorCanSave = true;
scope.showEditor = function(event) {
var view = this.schema;
var record = _.extend({}, event.record);
record[view.eventStart] = event.start;
record[view.eventStop] = event.end;
if (!editor) {
editor = ViewService.compile('<div ui-editor-popup></div>')(scope.$new());
editor.data('$target', element);
}
var popup = editor.isolateScope();
popup.show(record, function(result) {
RecordManager.add(result);
});
popup.waitForActions(function() {
if (!record || !record.id) {
popup.$broadcast("on:new");
} else {
popup.setEditable(scope.isEditable());
}
});
};
scope.isEditable = function() {
return editable;
};
scope.refresh = function(record) {
if (calRange.start && calRange.end) {
return RecordManager.events(calRange.start, calRange.end, options.timezone, function () {});
}
return main.fullCalendar("refetchEvents");
};
scope.filterEvents = function() {
RecordManager.filter();
};
scope.pagerText = function() {
return main.fullCalendar("getView").title;
};
scope.isMode = function(name) {
return mode === name;
};
scope.onMode = function(name) {
mode = name;
if (name === "week") {
name = scope.isAgenda() ? "agendaWeek" : "basicWeek";
}
if (name === "day") {
name = scope.isAgenda() ? "agendaDay" : "basicDay";
}
main.fullCalendar("changeView", name);
};
scope.onRefresh = function () {
scope.refresh();
};
scope.onNext = function() {
main.fullCalendar('next');
};
scope.onPrev = function() {
main.fullCalendar('prev');
};
scope.onToday = function() {
main.fullCalendar('today');
};
scope.onChangeHandler = null;
if (schema.onChange) {
scope.onChangeHandler = ActionService.handler(scope, element, {
action: schema.onChange
});
}
function adjustSize() {
if (main.is(':hidden')) {
return;
}
hideBubble();
main.fullCalendar('render');
main.fullCalendar('option', 'height', element.height());
legend.css("max-height", legend.parent().height() - mini.height()
- (parseInt(legend.css('marginTop')) || 0)
- (parseInt(legend.css('marginBottom')) || 0));
}
scope.$onAdjust(adjustSize, 100);
scope.$callWhen(function () {
return main.is(':visible');
}, function() {
element.parents('.view-container:first').css('overflow', 'inherit');
scope.onMode(mode);
adjustSize();
}, 100);
}
return {
link: function(scope, element, attrs, controller) {
scope._viewPromise.then(function(){
link(scope, element, attrs, controller);
});
},
replace: true,
template:
'<div>'+
'<div class="calendar-main" ui-attach-scroll=".fc-scroller"></div>'+
'<div class="calendar-side">'+
'<div class="calendar-mini"></div>'+
'<form class="form calendar-legend">'+
'<label class="checkbox" ng-repeat="color in getColors()" style="color: {{color.color.bc}}">'+
'<input type="checkbox" ng-click="filterEvents()" ng-model="color.checked"> {{color.title}}</label>'+
'</div>'+
'</div>'+
'</div>'
};
}]);
})();

View File

@ -0,0 +1,991 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function(){
/* global d3: true, nv: true, D3Funnel: true, RadarChart: true, GaugeChart: true */
"use strict";
var ui = angular.module('axelor.ui');
ui.ChartCtrl = ChartCtrl;
ui.ChartCtrl.$inject = ['$scope', '$element', '$http', 'ActionService'];
function ChartCtrl($scope, $element, $http, ActionService) {
var views = $scope._views;
var view = $scope.view = views.chart;
var viewChart = null;
var searchScope = null;
var clickHandler = null;
var actionHandler = null;
var loading = false;
var unwatch = null;
function refresh() {
if (viewChart && searchScope && $scope.searchFields && !searchScope.isValid()) {
return;
}
var context = $scope._context || {};
if ($scope.getContext) {
context = _.extend({}, $scope.getContext(), context);
}
if (searchScope) {
context = _.extend({}, context, searchScope.getContext());
}
context = _.extend({}, context, { _domainAction: $scope._viewAction });
loading = true;
var params = {
data: context
};
if (viewChart) {
params.fields = ['dataset'];
}
return $http.post('ws/meta/chart/' + view.name, params).then(function(response) {
var res = response.data;
var data = res.data;
var isInitial = viewChart === null;
if (viewChart === null) {
viewChart = data;
if (data.config && data.config.onClick) {
clickHandler = ActionService.handler($scope, $element, {
action: data.config.onClick
});
}
if (data.config && data.config.onAction) {
actionHandler = ActionService.handler($scope, $element, {
action: data.config.onAction
});
}
if (data.config && data.config.onActionTitle) {
$scope.actionTitle = data.config.onActionTitle;
}
} else {
data = _.extend({}, viewChart, data);
}
if ($scope.searchFields === undefined && data.search) {
$scope.searchFields = data.search;
$scope.searchInit = data.onInit;
$scope.usingSQL = data.usingSQL;
} else {
$scope.render(data);
if (isInitial) {
refresh(); // force loading data
}
}
loading = false;
}, function () {
loading = false;
});
}
$scope.setSearchScope = function (formScope) {
searchScope = formScope;
};
$scope.hasAction = function () {
return !!actionHandler;
};
$scope.handleAction = function (data) {
if (actionHandler) {
actionHandler._getContext = function () {
return _.extend({}, { _data: data }, {
_model: $scope._model || 'com.axelor.meta.db.MetaView',
_chart: view.name
});
};
actionHandler.handle();
}
};
$scope.handleClick = function (e) {
if (clickHandler) {
clickHandler._getContext = function () {
return _.extend({}, e.data.raw, {
_model: $scope._model || 'com.axelor.meta.db.MetaView',
_chart: view.name
});
};
clickHandler.handle();
}
};
$scope.onRefresh = function(force) {
if (unwatch || loading) {
return;
}
// in case of onInit
if ($scope.searchInit && !(searchScope||{}).record && !force) {
return;
}
unwatch = $scope.$watch(function chartRefreshWatch() {
if ($element.is(":hidden")) {
return;
}
unwatch();
unwatch = null;
refresh();
});
};
$scope.render = function(data) {
};
// refresh to load chart
$scope.onRefresh();
}
ChartFormCtrl.$inject = ['$scope', '$element', 'ViewService', 'DataSource'];
function ChartFormCtrl($scope, $element, ViewService, DataSource) {
$scope._dataSource = DataSource.create('com.axelor.meta.db.MetaView');
ui.FormViewCtrl.call(this, $scope, $element);
$scope.setEditable();
$scope.setSearchScope($scope);
function fixFields(fields) {
_.each(fields, function(field){
if (field.type == 'reference') {
field.type = 'MANY_TO_ONE';
field.canNew = false;
field.canEdit = false;
}
if (field.type)
field.type = field.type.toUpperCase();
else
field.type = 'STRING';
});
return fields;
}
var unwatch = $scope.$watch('searchFields', function chartSearchFieldsWatch(fields) {
if (!fields) {
return;
}
unwatch();
var meta = { fields: fixFields(fields) };
var view = {
type: 'form',
items: [{
type: 'panel',
noframe: true,
items: _.map(meta.fields, function (item) {
var props = _.extend({}, item, {
showTitle: false,
placeholder: item.title || item.autoTitle
});
if (item.multiple && (item.target || item.selection)) {
item.widget = item.target ? "TagSelect" : "MultiSelect";
}
return props;
})
}]
};
ViewService.process(meta, view);
view.onLoad = $scope.searchInit;
$scope.fields = meta.fields;
$scope.schema = view;
$scope.schema.loaded = true;
var interval;
function reload() {
$scope.$parent.onRefresh();
$scope.$applyAsync();
}
function delayedReload() {
clearTimeout(interval);
interval = setTimeout(reload, 500);
}
function onNewOrEdit() {
if ($scope.$events.onLoad) {
$scope.$events.onLoad().then(delayedReload);
}
}
var __getContext = $scope.getContext;
$scope.getContext = function () {
var ctx = __getContext.call(this);
_.each(meta.fields, function (item) {
if (item.multiple && (item.target || item.selection)) {
var value = ctx[item.name];
if (_.isArray(value)) value = _.pluck(value, "id");
if (_.isString(value)) value = value.split(/\s*,\s*/g);
ctx[item.name] = value;
} else if (item.target && $scope.usingSQL) {
var value = ctx[item.name];
if (value) {
ctx[item.name] = value.id;
}
}
});
return ctx;
};
$scope.$on('on:new', onNewOrEdit);
$scope.$on('on:edit', onNewOrEdit);
$scope.$watch('record', function chartSearchRecordWatch(record) {
if (interval === undefined) {
interval = null;
return;
}
if ($scope.isValid()) delayedReload();
}, true);
$scope.$watch('$events.onLoad', function chartOnLoadWatch(handler) {
if (handler) {
handler().then(delayedReload);
}
});
});
}
function $conv(value, type) {
if (!value && type === 'text') return 'N/A';
if (!value) return 0;
if (_.isNumber(value)) return value;
if (/^(-)?\d+(\.\d+)?$/.test(value)) {
return +value;
}
return value;
}
function applyXY(chart, data) {
var type = data.xType;
chart.y(function (d) { return d.y; });
if (type == "date") {
return chart.x(function (d) { return moment(d.x).toDate(); });
}
return chart.x(function (d) { return d.x; });
}
var themes = {
// default
d3: d3.scale.category10().range(),
// material
material: [
'#f44336', // Red
'#E91E63', // Pink
'#9c27b0', // Purple
'#673ab7', // Deep Purple
'#3f51b5', // Indigo
'#2196F3', // Blue
'#03a9f4', // Light Blue
'#00bcd4', // Cyan
'#009688', // Teal
'#4caf50', // Green
'#8bc34a', // Light Green
'#cddc39', // Lime
'#ffeb3b', // Yellow
'#ffc107', // Amber
'#ff9800', // Orange
'#ff5722', // Deep Orange
'#795548', // Brown
'#9e9e9e', // Grey
'#607d8b', // Blue Grey
],
// chart.js
chartjs: [
'#ff6384', '#ff9f40', '#ffcd56', '#4bc0c0',
'#36a2eb', '#9966ff', '#c9cbcf',
],
// echart - roma
roma: [
'#E01F54','#001852','#f5e8c8','#b8d2c7','#c6b38e',
'#a4d8c2','#f3d999','#d3758f','#dcc392','#2e4783',
'#82b6e9','#ff6347','#a092f1','#0a915d','#eaf889',
'#6699FF','#ff6666','#3cb371','#d5b158','#38b6b6',
],
// echart - macarons
macarons: [
'#2ec7c9','#b6a2de','#5ab1ef','#ffb980','#d87a80',
'#8d98b3','#e5cf0d','#97b552','#95706d','#dc69aa',
'#07a2a4','#9a7fd1','#588dd5','#f5994e','#c05050',
'#59678c','#c9ab00','#7eb00a','#6f5553','#c14089',
]
};
function colors(names, shades, type) {
var given = themes[names] ? themes[names] : names;
given = given || themes.material;
given = _.isArray(given) ? given : given.split(',');
if (given && shades > 1) {
var n = Math.max(0, Math.min(+(shades) || 4, 4));
return _.flatten(given.map(function (c) {
return _.first(_.range(0, n + 1).map(d3.scale.linear().domain([0, n + 1]).range([c, 'white'])), n);
}));
}
return given;
}
var CHARTS = {};
function PlusData(series, data) {
var result = _.chain(data.dataset)
.groupBy(data.xAxis)
.map(function (group, name) {
var value = 0;
_.each(group, function (item) {
value += $conv(item[series.key]);
});
var raw = {};
if (group[0]) {
raw[data.xAxis] = name;
raw[series.key] = value;
raw[data.xAxis + 'Id'] = group[0][data.xAxis + 'Id'];
}
return {
x: name === 'null' ? 'N/A' : name,
y: value,
raw: raw
};
}).value();
return result;
}
function PlotData(series, data) {
var ticks = _.chain(data.dataset).pluck(data.xAxis).unique().map(function (v) { return $conv(v, data.xType); }).value();
var groupBy = series.groupBy;
var datum = [];
_.chain(data.dataset).groupBy(groupBy)
.map(function (group, groupName) {
var name = groupBy ? groupName : null;
var values = _.map(group, function (item) {
var x = $conv(item[data.xAxis], data.xType) || 0;
var y = $conv(item[series.key] || name || 0);
return { x: x, y: y, raw: item };
});
var my = _.pluck(values, 'x');
var missing = _.difference(ticks, my);
if (ticks.length === missing.length) {
return;
}
_.each(missing, function(x) {
values.push({ x: x, y: 0 });
});
values = _.sortBy(values, 'x');
datum.push({
key: name || series.title,
type: series.type,
values: values
});
});
return datum;
}
function PieChart(scope, element, data) {
var series = _.first(data.series);
var datum = PlusData(series, data);
var config = data.config || {};
var chart = nv.models.pieChart()
.showLabels(false)
.height(null)
.width(null)
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
if (series.type === "donut") {
chart.donut(true)
.donutRatio(0.40);
}
if (_.toBoolean(config.percent)) {
chart.showLabels(true)
.labelType("percent")
.labelThreshold(0.05);
}
d3.select(element[0])
.datum(datum)
.transition().duration(1200).call(chart);
chart.pie.dispatch.on('elementClick', function (e) {
scope.handleClick(e);
});
return chart;
}
CHARTS.pie = PieChart;
CHARTS.donut = PieChart;
function DBarChart(scope, element, data) {
var series = _.first(data.series);
var datum = PlusData(series, data);
datum = [{
key: data.title,
values: datum
}];
var chart = nv.models.discreteBarChart()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.staggerLabels(true)
.showValues(true);
d3.select(element[0])
.datum(datum)
.transition().duration(500).call(chart);
chart.discretebar.dispatch.on('elementClick', function (e) {
scope.handleClick(e);
});
return chart;
}
function BarChart(scope, element, data) {
var series = _.first(data.series);
var datum = PlotData(series, data);
var chart = nv.models.multiBarChart()
.reduceXTicks(false);
chart.multibar.hideable(true);
chart.stacked(data.stacked);
d3.select(element[0])
.datum(datum)
.transition().duration(500).call(chart);
chart.multibar.dispatch.on('elementClick', function (e) {
scope.handleClick(e);
});
return chart;
}
function HBarChart(scope, element, data) {
var series = _.first(data.series);
var datum = PlotData(series, data);
var chart = nv.models.multiBarHorizontalChart();
chart.stacked(data.stacked);
d3.select(element[0])
.datum(datum)
.transition().duration(500).call(chart);
chart.multibar.dispatch.on('elementClick', function (e) {
scope.handleClick(e);
});
return chart;
}
function FunnelChart(scope, element, data) {
if(!data.dataset){
return;
}
var chart = new D3Funnel(element[0]);
var w = element.width();
var h = element.height();
var config = _.extend({}, data.config);
var props = {
fillType: 'gradient',
hoverEffects: true,
dynamicArea: true,
animation: 200};
if(config.width){
props.width = w*config.width/100;
}
if(config.height){
props.height = h*config.height/100;
}
var series = _.first(data.series) || {};
var opts = [];
_.each(data.dataset, function(dat){
opts.push([dat[data.xAxis],($conv(dat[series.key])||0)]);
});
chart.draw(opts, props);
chart.update = function(){};
return chart;
}
CHARTS.bar = BarChart;
CHARTS.dbar = DBarChart;
CHARTS.hbar = HBarChart;
CHARTS.funnel = FunnelChart;
function LineChart(scope, element, data) {
var series = _.first(data.series);
var datum = PlotData(series, data);
var chart = nv.models.lineChart()
.showLegend(true)
.showYAxis(true)
.showXAxis(true);
applyXY(chart, data);
d3.select(element[0])
.datum(datum)
.transition().duration(500).call(chart);
return chart;
}
function AreaChart(scope, element, data) {
var series = _.first(data.series);
var datum = PlotData(series, data);
var chart = nv.models.stackedAreaChart();
applyXY(chart, data);
d3.select(element[0])
.datum(datum)
.transition().duration(500).call(chart);
return chart;
}
CHARTS.line = LineChart;
CHARTS.area = AreaChart;
function RadarCharter(scope, element, data) {
var result = _.map(data.dataset, function(item) {
return _.map(data.series, function(s) {
var title = s.title || s.key,
value = item[s.key];
return {
axis: title,
value: $conv(value) || 0
};
});
});
var id = _.uniqueId('_radarChart'),
parent = element.parent();
parent.attr('id', id)
.addClass('radar-chart')
.empty();
var size = Math.min(parent.innerWidth(), parent.innerHeight());
RadarChart.draw('#'+id, result, {
w: size,
h: size
});
parent.children('svg')
.css('width', 'auto')
.css('margin', 'auto')
.css('margin-top', 10);
return null;
}
function GaugeCharter(scope, element, data) {
var config = data.config,
min = +(config.min) || 0,
max = +(config.max) || 100,
value = 0;
var item = _.first(data.dataset),
series = _.first(data.series),
key = series.key || data.xAxis;
if (item) {
value = item[key] || value;
}
var w = element.width();
var h = element.height();
var parent = element.hide().parent();
parent.children('svg').remove();
parent.append(element);
var chart = GaugeChart(parent[0], {
size: 300,
clipWidth: 300,
clipHeight: h,
ringWidth: 60,
minValue: min,
maxValue: max,
transitionMs: 4000
});
chart.render();
chart.update(value);
parent.children('svg:last')
.css('display', 'block')
.css('width', 'auto')
.css('margin', 'auto')
.css('margin-top', 0);
}
function TextChart(scope, element, data) {
var config = _.extend({
strong: true,
shadow: false,
fontSize: 22
}, data.config);
var values = _.first(data.dataset) || {};
var series = _.first(data.series) || {};
var value = values[series.key];
if (config.format) {
value = _t(config.format, value);
}
var svg = d3.select(element.empty()[0]);
var text = svg.append("svg:text")
.attr("x", "50%")
.attr("y", "50%")
.attr("dy", ".3em")
.attr("text-anchor", "middle")
.text(value);
if (config.color) text.attr("fill", config.color);
if (config.fontSize) text.style("font-size", config.fontSize);
if (_.toBoolean(config.strong)) text.style("font-weight", "bold");
if (_.toBoolean(config.shadow)) text.style("text-shadow", "0 1px 2px rgba(0, 0, 0, .5)");
}
CHARTS.text = TextChart;
CHARTS.radar = RadarCharter;
CHARTS.gauge = GaugeCharter;
function Chart(scope, element, data) {
var type = null;
var config = data.config || {};
for(var i = 0 ; i < data.series.length ; i++) {
type = data.series[i].type;
if (type === "bar" && !data.series[i].groupBy) type = "dbar";
if (type === "pie" || type === "dbar" || type === "radar" || type === "gauge") {
break;
}
}
if (type === "pie" && data.series.length > 1) {
return;
}
if (type !== "radar" && data.series.length > 1) {
type = "multi";
}
// clean up last instance
(function () {
var chart = element.off('adjustSize').empty().data('chart');
if (chart && chart.tooltip && chart.tooltip.id) {
d3.select('#' + chart.tooltip.id()).remove();
}
})();
nv.addGraph(function generate() {
var noData = _t('No records found.');
if (data.dataset && data.dataset.stacktrace) {
noData = data.dataset.message;
data.dataset = [];
}
var maker = CHARTS[type] || CHARTS.bar || function () {};
var chart = maker(scope, element, data);
if (!chart) {
return;
}
// series scale attribute
var series = _.first(data.series);
var scale = series && series.scale;
// format as integer if no scale is specified
// and data has integer series values
if (!isInteger(scale) && hasIntegerValues(data)) {
scale = 0;
}
if (isInteger(scale)) {
var format = '.' + scale + 'f';
chart.yAxis && chart.yAxis.tickFormat(d3.format(format));
chart.valueFormat && chart.valueFormat(d3.format(format));
}
if (chart.color) {
chart.color(colors(config.colors, config.shades, type));
}
if (chart.noData) {
chart.noData(noData);
}
if(chart.controlLabels) {
chart.controlLabels({
grouped: _t('Grouped'),
stacked: _t('Stacked'),
stream: _t('Stream'),
expanded: _t('Expanded'),
stack_percent: _t('Stack %')
});
}
var tickFormats = {
"date" : function (d) {
var f = config.xFormat;
return moment(d).format(f || 'YYYY-MM-DD');
},
"month" : function(d) {
var v = "" + d;
var f = config.xFormat;
if (v.indexOf(".") > -1) return "";
if (_.isString(d) && /^(\d+)$/.test(d)) {
d = parseInt(d);
}
if (_.isNumber(d)) {
return moment([moment().year(), d - 1, 1]).format(f || "MMM");
}
if (_.isString(d) && d.indexOf('-') > 0) {
return moment(d).format(f || 'MMM, YYYY');
}
return d;
},
"year" : function(d) {
return moment([moment().year(), d - 1, 1]).format("YYYY");
},
"number": d3.format(',f'),
"decimal": d3.format(',.1f'),
"text": function(d) { return d; }
};
var tickFormat = tickFormats[data.xType];
if (chart.xAxis && tickFormat) {
chart.xAxis
.rotateLabels(-45)
.tickFormat(tickFormat);
}
if (chart.yAxis && data.yTitle) {
chart.yAxis.axisLabel(data.yTitle);
}
var margin = data.xType === 'date' ? { 'bottom': 65 } : null;
['top', 'left', 'bottom', 'right'].forEach(function (side) {
var key = 'margin-' + side;
var val = parseInt(config[key]);
if (val) {
(margin||(margin={}))[side] = val;
}
});
if (chart.margin && margin) {
chart.margin(margin);
}
var lastWidth = 0;
var lastHeight = 0;
function adjust() {
if (!element[0] || element.parent().is(":hidden")) {
return;
}
var rect = element[0].getBoundingClientRect();
var w = rect.width,
h = rect.height;
if (w === lastWidth && h === lastHeight) {
return;
}
lastWidth = w;
lastHeight = h;
chart.update();
}
element.data('chart', chart);
scope.$onAdjust(adjust, 100);
setTimeout(chart.update, 10);
return chart;
});
}
function hasIntegerValues(data) {
var series = _.first(data.series);
var dataset = _.first(data.dataset);
return series && dataset && isInteger(dataset[series.key]);
}
function isInteger(n) {
return (n ^ 0) === n;
}
var directiveFn = function(){
return {
controller: ChartCtrl,
link: function(scope, element, attrs) {
var svg = element.children('svg');
var form = element.children('.chart-controls');
function doExport(data) {
var dataset = data.dataset || [];
var header = [];
_.each(dataset, function (item) {
header = _.unique(_.flatten([header, _.keys(item)]));
});
var content = "data:text/csv;charset=utf-8," + header.join(';') + '\n';
dataset.forEach(function (item) {
var row = header.map(function (key) {
var val = item[key];
if (val === undefined || val === null) {
val = '';
}
return '"' + (''+val).replace(/"/g, '""') + '"';
});
content += row.join(';') + '\n';
});
var name = (data.title || 'export').toLowerCase();
ui.download(encodeURI(content), _.underscored(name) + '.csv');
}
scope.render = function(data) {
if (element.is(":hidden")) {
return;
}
setTimeout(function () {
svg.height(element.height() - form.height()).width('100%');
if (!scope.dashlet || !scope.dashlet.title) {
scope.title = data.title;
}
Chart(scope, svg, data);
var canExport = data && _.isArray(data.dataset);
scope.canExport = function () {
return canExport;
};
scope.onExport = function () {
doExport(data);
};
scope.onAction = function () {
scope.handleAction(data && data.dataset);
};
return;
});
};
function onNewOrEdit() {
if (scope.searchInit && scope.searchFields) {
return;
}
scope.onRefresh(true);
}
scope.$on('on:new', onNewOrEdit);
scope.$on('on:edit', onNewOrEdit);
},
replace: true,
template:
'<div class="chart-container" style="background-color: white; ">'+
'<div ui-chart-form></div>'+
'<svg></svg>'+
'</div>'
};
};
ui.directive('uiChartForm', function () {
return {
scope: true,
controller: ChartFormCtrl,
link: function (scope, element, attrs, ctrls) {
},
replace: true,
template:
"<div class='chart-controls'>" +
"<div ui-view-form x-handler='this'></div>" +
"</div>"
};
});
ui.directive('uiViewChart', directiveFn);
ui.directive('uiPortletChart', directiveFn);
})();

View File

@ -0,0 +1,258 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module("axelor.ui");
CustomViewCtrl.$inject = ['$scope', '$http', 'DataSource', 'ViewService'];
function CustomViewCtrl($scope, $http, DataSource, ViewService) {
ui.ViewCtrl.call(this, $scope, DataSource, ViewService);
var view = $scope._views.custom || {};
var viewPromise = null;
$scope.show = function() {
if (!viewPromise) {
viewPromise = $scope.loadView('custom', view.name);
viewPromise.then(function(meta) {
var schema = meta.view;
$scope.schema = schema;
$scope.schema.loaded = true;
});
}
$scope.onShow(viewPromise);
};
$scope.onShow = function(viewPromise) {
// it will be refreshed by dashlet
if ($scope.dashlet) {
return;
}
viewPromise.then(function () {
$scope.onRefresh();
});
};
$scope.getRouteOptions = function() {
return {
mode: 'custom'
};
};
$scope.setRouteOptions = function(options) {
var opts = options || {};
if (opts.mode === "custom") {
return $scope.updateRoute();
}
var params = $scope._viewParams;
if (params.viewType !== "custom") {
return $scope.show();
}
};
$scope.getContext = function () {
var context = $scope._context || {};
if ($scope.$parent.getContext) {
context = _.extend({}, $scope.$parent.getContext(), context);
}
return context;
};
$scope.onRefresh = function() {
var context = $scope.getContext();
var params = {
data: context
};
return $http.post('ws/meta/custom/' + view.name, params).then(function(response) {
var res = response.data;
$scope.data = (res.data||{}).dataset;
});
};
}
var customDirective = ["$compile", function ($compile) {
return {
controller: CustomViewCtrl,
link: function (scope, element, attrs, ctrl) {
var evalScope = axelor.$evalScope(scope);
function render(template) {
var elem = $('<span>' + axelor.sanitize(template.trim()) + '</span>');
if (elem.children().length === 1) {
elem = elem.children().first();
}
if (scope.schema && scope.schema.css) {
element.parents(".dashlet:first").addClass(scope.schema.css);
}
elem = $compile(elem)(evalScope);
element.append(elem);
}
var unwatch = scope.$watch('schema.template', function customTemplateWatch(template) {
if (template) {
unwatch();
render(template);
}
});
scope.showToggle = false;
scope.$watch('data', function customDataWatch(data) {
evalScope.data = data;
evalScope.first = _.first(data);
});
}
};
}];
ui.directive('uiCustomView', customDirective);
ui.directive('uiPortletCustom', customDirective);
// helper directives
ui.directive('reportBox', function() {
return {
scope: {
value: '=',
label: '@',
percent: '=',
up: '=',
tag: '=',
tagCss: '='
},
link: function (scope, element, attrs) {
setTimeout(function () {
element.parents('.dashlet:first')
.addClass("report-box");
});
},
replace: true,
template:
"<div class='report-box'>" +
"<h1>{{value}}</h1>" +
"<small>{{label}}</small>" +
"<div class='font-bold text-info pull-right' ng-show='percent'>" +
"<span>{{percent}}</span> <i class='fa fa-level-up'></i>" +
"</div>" +
"<div class='report-tags' ng-if='tag'><span class='label' ng-class='tagCss'>{{tag}}</span></div>" +
"</div>"
};
});
ui.directive('reportTable', function() {
return {
scope: {
data: '=',
columns: '@',
sums: '@'
},
link: function (scope, element, attrs) {
var cols = [];
var sums = (scope.sums||'').split(',');
var fields = {};
var schema = scope.$parent.$parent.schema;
function makeColumns(names) {
cols = [];
fields = {};
_.each(names, function (name) {
var field = _.findWhere(schema.items, { name: name }) || {};
var col = _.extend({}, field, field.widgetAttrs, {
name: name,
title: _.humanize(name)
});
fields[name] = col;
cols.push(col);
});
scope.cols = cols;
}
if (scope.columns) {
makeColumns((scope.columns||'').split(','));
} else {
var unwatch = scope.$watch('data', function reportDataWatch(data) {
if (data) {
unwatch();
var first = _.first(data) || {};
var names = _.keys(first).filter(function (name) { return name !== '$$hashKey'; });
makeColumns(names.sort());
}
});
}
scope.sums = sums;
scope.format = function(value, name) {
if (value === null || value === undefined) {
return "";
}
var field = fields[name];
if (field && field.scale) {
var val = +(value);
if (_.isNumber(val)) {
return val.toFixed(field.scale);
}
}
return value;
};
scope.sum = function (name) {
if (sums.indexOf(name) === -1) {
return "";
}
var res = 0.0;
_.each(scope.data, function (row) {
var val = +(row[name]) || 0;
res += val;
});
return scope.format(res, name);
};
setTimeout(function () {
element.parents('.dashlet:first')
.addClass("report-table");
});
},
replace: true,
template:
"<table class='table table-striped'>" +
"<thead>" +
"<tr>" +
"<th ng-repeat='col in cols'>{{col.title}}</th>" +
"</tr>" +
"</thead>" +
"<tbody>" +
"<tr ng-repeat='row in data'>" +
"<td ng-repeat='col in cols'>{{format(row[col.name], col.name)}}</td>" +
"</tr>" +
"</tbody>" +
"<tfoot ng-if='sums.length'>" +
"<tr>" +
"<td ng-repeat='col in cols'>{{sum(col.name)}}</td>" +
"</tr>" +
"</tfoot>" +
"</table>"
};
});
})();

View File

@ -0,0 +1,335 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
DashboardCtrl.$inject = ['$scope', '$element'];
function DashboardCtrl($scope, $element) {
var view = $scope._views.dashboard;
if (view.items) {
$scope.$timeout(function () {
$scope.parse(view);
});
} else {
$scope.loadView('dashboard', view.name).success(function(fields, schema){
$scope.parse(schema);
});
}
$scope.$applyAsync(function(){
if (view.deferred)
view.deferred.resolve($scope);
});
$scope.show = function(promise) {
$scope.updateRoute();
};
$scope.onShow = function() {
};
$scope.getContext = function() {
return _.extend({}, $scope._context);
};
$scope.getRouteOptions = function() {
return {
mode: 'dashboard',
args: []
};
};
$scope.setRouteOptions = function(options) {
if (!$scope.isNested) {
$scope.updateRoute();
}
};
$scope.parse = function(schema) {
var items = angular.copy(schema.items || []);
var row = [];
items.forEach(function (item, i) {
var span = item.colSpan || 6;
item.$index = i;
item.spanCss = {};
item.spanCss['dashlet-cs' + span] = true;
row.push(item);
});
$scope.schema = schema;
$scope.row = row;
};
}
ui.directive('uiViewDashboard', ['ViewService', function(ViewService) {
return {
controller: DashboardCtrl,
link: function(scope, element, attrs) {
scope.sortableOptions = {
handle: ".dashlet-header",
cancel: ".dashlet-buttons",
items: ".dashlet",
tolerance: "pointer",
activate: function(e, ui) {
var height = ui.helper.height();
ui.placeholder.height(height);
},
deactivate: function(event, ui) {
axelor.$adjustSize();
},
stop: function (event, ui) {
var schema = scope.schema;
var items = _.map(scope.row, function (item) {
return schema.items[item.$index];
});
if (angular.equals(schema.items, items)) {
return;
}
schema.items = items;
return ViewService.save(schema);
}
};
var unwatch = scope.$watch("schema", function dashboardSchemaWatch(schema) {
if (!schema) {
return;
}
unwatch();
if (schema.css) {
element.addClass(schema.css);
}
});
},
replace: true,
transclude: true,
template:
"<div ui-sortable='sortableOptions' ng-model='row'>" +
"<div class='dashlet' ng-class='dashlet.spanCss' ng-repeat='dashlet in row' ui-view-dashlet></div>" +
"</div>"
};
}]);
DashletCtrl.$inject = ['$scope', '$element', 'MenuService', 'DataSource', 'ViewService'];
function DashletCtrl($scope, $element, MenuService, DataSource, ViewService) {
var self = this;
var init = _.once(function init() {
ui.ViewCtrl.call(self, $scope, DataSource, ViewService);
$scope.show = function() {
};
$scope.onShow = function() {
};
$scope.$on('on:attrs-change:refresh', function(e) {
e.preventDefault();
if ($scope.onRefresh) {
$scope.onRefresh();
}
});
$scope.$on('on:tab-reload', function(e) {
if ($scope.onRefresh) {
$scope.onRefresh();
}
});
});
$scope.initDashlet = function(dashlet, options) {
var action = dashlet.action;
if (!action) {
return init();
}
MenuService.action(action, options).success(function(result){
if (_.isEmpty(result.data)) {
return;
}
var view = result.data[0].view;
$scope._viewParams = view;
$scope._viewAction = action;
init();
$scope.title = dashlet.title || view.title;
if ($scope.attr) {
$scope.title = $scope.attr('title') || $scope.title;
}
$scope.parseDashlet(dashlet, view);
});
};
}
ui.directive('uiViewDashlet', ['$compile', function($compile){
return {
scope: true,
controller: DashletCtrl,
link: function(scope, element, attrs) {
var lazy = true;
(function () {
var counter = 0;
return function checkLoading() {
if (counter < 10 && element.parent().is(":hidden")) {
counter++;
return setTimeout(checkLoading, 100);
}
lazy = !element.parent().is(".dashlet-row");
scope.waitForActions(function () {
var unwatch = scope.$watch(function dashletInitWatch() {
var dashlet = scope.dashlet;
if (!dashlet) {
return;
}
if (element.parent().is(":hidden")) {
lazy = true;
return;
}
unwatch();
unwatch = null;
var ctx;
if (scope.getContext) {
ctx = scope.getContext();
}
scope.initDashlet(dashlet, {
context: ctx
});
});
});
};
})()();
scope.parseDashlet = _.once(function(dashlet, view) {
var body = element.find('.dashlet-body:first');
var header = element.find('.dashlet-header:first');
var template = $('<div ui-portlet-' + view.viewType + '></div>');
scope.noFilter = !dashlet.canSearch;
template = $compile(template)(scope);
body.append(template);
if (dashlet.height) {
setTimeout(function() {
body.css("height", Math.max(0, dashlet.height - header.outerHeight()));
});
}
if (dashlet.css) {
element.addClass(dashlet.css);
}
if (view && view.viewType) {
element.addClass(view.viewType);
}
element.removeClass('hidden');
scope.show();
// if lazy, load data
if (scope.onRefresh && lazy) {
scope.onRefresh();
}
});
scope.showPager = false;
scope.showRefresh = true;
scope.showToggle = true;
scope.collapsed = false;
scope.collapsedIcon = "fa-chevron-up";
scope.onDashletToggle = function(event) {
var body = element.children('.dashlet-body');
var action = scope.collapsed ? "show" : "hide";
scope.collapsed = !scope.collapsed;
scope.collapsedIcon = scope.collapsed ? "fa-chevron-down" : "fa-chevron-up";
element.removeClass("collapsed");
body[action]("blind", 200, function () {
element.toggleClass("collapsed", !!scope.collapsed);
if (body.css('display') !== 'none' && action === 'hide') {
body.hide();
}
axelor.$adjustSize();
});
};
scope.doNext = function() {
if (this.canNext()) this.onNext();
};
scope.doPrev = function() {
if (this.canPrev()) this.onPrev();
};
},
replace: true,
template:
"<div class='dashlet hidden'>" +
"<div class='dashlet-header'>" +
"<ul class='dashlet-buttons pull-right' ng-if='showRefresh || canExport() || hasAction()'>" +
"<li class='dropdown'>" +
"<a href='' class='dropdown-toggle' data-toggle='dropdown'><i class='fa fa-gear'></i></a>" +
"<ul class='dropdown-menu pull-right'>" +
"<li ng-if='showRefresh'>" +
"<a href='' ng-click='onRefresh()' x-translate>Refresh</a>" +
"</li>" +
"<li ng-if='canExport()'>" +
"<a href='' ng-click='onExport()' x-translate>Export</a>" +
"</li>" +
"<li ng-if='hasAction()' class='divider'></li>" +
"<li ng-if='hasAction()'>" +
"<a href='' ng-click='onAction()'>{{ (actionTitle || _t('Action')) }}</a>" +
"</li>" +
"</ul>" +
"</li>" +
"<li ng-if='showToggle'><a href='' ng-click='onDashletToggle()'><i class='fa' ng-class='collapsedIcon'></i></a></li>" +
"</ul>" +
"<div class='dashlet-pager pull-right' ng-if='showPager'>" +
"<span class='dashlet-pager-text'>{{pagerText()}}</span>" +
"<a href='' ng-click='doPrev()' ng-class='{disabled: !canPrev()}'><i class='fa fa-step-backward'></i></a>" +
"<a href='' ng-click='doNext()' ng-class='{disabled: !canNext()}'><i class='fa fa-step-forward'></i></a>" +
"</div>" +
"<div class='dashlet-title'>{{title}}</div>" +
"</div>" +
"<div class='dashlet-body'></div>" +
"</div>"
};
}]);
})();

1812
sophal/js/view/view.dms.js Normal file

File diff suppressed because it is too large Load Diff

1507
sophal/js/view/view.form.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,835 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// localization
(function () {
/* global gantt: true */
"use strict";
$(function () {
$('<script>')
.attr('type', 'text/javascript')
.attr('src', 'https://export.dhtmlx.com/gantt/api.js').appendTo('head');
});
var regional = {
month_full: [
_t('January'),
_t('February'),
_t('March'),
_t('April'),
_t('May'),
_t('June'),
_t('July'),
_t('August'),
_t('September'),
_t('October'),
_t('November'),
_t('December')],
month_short: [
_t('Jan'),
_t('Feb'),
_t('Mar'),
_t('Apr'),
_t('May'),
_t('Jun'),
_t('Jul'),
_t('Aug'),
_t('Sep'),
_t('Oct'),
_t('Nov'),
_t('Dec')],
day_full: [
_t('Sunday'),
_t('Monday'),
_t('Tuesday'),
_t('Wednesday'),
_t('Thursday'),
_t('Friday'),
_t('Saturday')],
day_short : [_t('Sun'), _t('Mon'), _t('Tue'), _t('Wed'), _t('Thu'), _t('Fri'), _t('Sat')]
};
gantt.locale = {
date: regional,
labels:{
new_task: _t("New task"),
icon_save: _t("Save"),
icon_cancel: _t("Cancel"),
icon_details: _t("Details"),
icon_edit: _t("Edit"),
icon_delete: _t("Delete"),
confirm_closing:"",// Your changes will be lost, are your sure ?
confirm_deleting: _t("Task will be deleted permanently, are you sure?"),
section_description: _t("Description"),
section_time: _t("Time period"),
section_type: _t("Type"),
/* grid columns */
column_text : _t("Task name"),
column_start_date : _t("Start time"),
column_duration : _t("Duration"),
column_add : "",
/* link confirmation */
link: _t("Link"),
confirm_link_deleting: _t("will be deleted"),
link_start: " " + _t("(start)"),
link_end: " " + _t("(end)"),
type_task: _t("Task"),
type_project: _t("Project"),
type_milestone: _t("Milestone"),
minutes: _t("Minutes"),
hours: _t("Hours"),
days: _t("Days"),
weeks: _t("Week"),
months: _t("Months"),
years: _t("Years")
}
};
var ui = angular.module('axelor.ui');
ui.controller('GanttViewCtrl', GanttViewCtrl);
GanttViewCtrl.$inject = ['$scope', '$element'];
function GanttViewCtrl($scope, $element) {
ui.DSViewCtrl('gantt', $scope, $element);
var ds = $scope._dataSource;
var view = $scope._views.gantt;
var initialized = false;
$scope.onShow = function(viewPromise) {
if (initialized) {
return $scope.refresh();
}
viewPromise.then(function(){
var schema = $scope.schema;
initialized = true;
$scope._viewResolver.resolve(schema, $element);
$scope.updateRoute();
});
};
$scope.select = function() {
};
$scope.fetchItems = function(callback) {
var schema = $scope.schema;
var searchFields = _.pluck(this.fields, "name");
searchFields.push(schema.taskStart);
var optionalFields = [schema.taskProgress,
schema.taskEnd,
schema.taskDuration,
schema.taskParent,
schema.taskSequence,
schema.taskProgress,
schema.finishToStart,
schema.startToStart,
schema.finishToFinish,
schema.startToFinish,
schema.taskUser
];
_.each(optionalFields,function(optField){
if(optField){
searchFields.push(optField);
}
});
var opts = {
fields: searchFields,
filter: false,
domain: this._domain,
store: false
};
ds.search(opts).success(function(records) {
callback(records);
});
};
$scope.getContext = function() {
return _.extend({}, $scope._context);
};
$scope.getRouteOptions = function() {
return {
mode: 'gantt',
args: []
};
};
$scope.setRouteOptions = function(options) {
var opts = options || {};
if (opts.mode === "gantt") {
return $scope.updateRoute();
}
var params = $scope._viewParams;
if (params.viewType !== "calendar") {
return $scope.show();
}
};
$scope.doSave = function(task, callback){
var record = _.clone(task.record);
return ds.save(record).success(function(res){
callback(task, res);
});
};
$scope.doRemove = function(id, task){
var record = _.clone(task.record);
return ds.remove(record).success(function(res){
return true;
});
};
}
ui.directive('uiViewGantt', ['ViewService', 'ActionService', function(ViewService, ActionService) {
function link(scope, element, attrs, controller) {
var main = element.children(".gantt-main");
var schema = scope.schema;
var fields = scope.fields;
var fieldNames = _.pluck(schema.items, "name");
var firstField = fields[fieldNames[0]];
var mode = schema.mode || "week";
var editor = null;
ganttInit();
function byId(list, id) {
for (var i = 0; i < list.length; i++) {
if (list[i].key == id)
return list[i].label || "";
}
return "";
}
function setScaleConfig(value){
switch (value) {
case "day":
gantt.config.scale_unit = "day";
gantt.config.date_scale = "%d/%m/%Y";
gantt.config.subscales = [{unit:"hour", step:1, date:"%H:%i"}];
gantt.templates.date_scale = null;
gantt.config.min_column_width = 50;
break;
case "week":
var weekScaleTemplate = function(date){
var dateToStr = gantt.date.date_to_str("%d/%m/%Y");
var endDate = gantt.date.add(gantt.date.add(date, 1, "week"), -1, "day");
return gantt.date.date_to_str("%W")(date) + "(" + dateToStr(date) + " - " + dateToStr(endDate) + ")";
};
gantt.config.scale_unit = "week";
gantt.templates.date_scale = weekScaleTemplate;
gantt.config.min_column_width = 50;
gantt.config.subscales = [
{unit:"day", step:1, date:"%D %d" }];
break;
case "month":
gantt.config.scale_unit = "month";
gantt.config.date_scale = "%F, %Y";
gantt.config.subscales = [
{unit:"week", step:1, date:"%W" }
];
gantt.templates.date_scale = null;
gantt.config.min_column_width = 50;
break;
case "year":
gantt.config.scale_unit = "year";
gantt.config.date_scale = "%Y";
gantt.templates.date_scale = null;
gantt.config.min_column_width = 100;
gantt.config.subscales = [
{unit:"month", step:1, date:"%M" }
];
break;
}
}
function getGanttColumns() {
var colHeader = '<div class="gantt_grid_head_cell gantt_grid_head_add" onclick="gantt.createTask()"></div>';
var colContent = function(task){
return '<i class="fa gantt_button_grid gantt_grid_add fa-plus" onclick="gantt.createTask(null, '+task.id+')"></i>'+
'<i class="fa gantt_button_grid gantt_grid_delete fa-times" onclick="gantt.confirm({ ' +
'title: gantt.locale.labels.confirm_deleting_title,'+
'text: gantt.locale.labels.confirm_deleting,'+
'callback: function(res){ '+
' if(res)'+
' gantt.deleteTask('+task.id+');'+
'}})"></i>';
};
var columns = [];
if (schema.taskUser) {
columns.push({name: "users", label: fields[schema.taskUser].title, align: "center", template: function (item) {
return byId(gantt.serverList("users"), item.user_id);
}});
}
var isTree = true;
_.each(fieldNames, function(fname){
var field = fields[fname];
if (columns.length == 0) {
columns.push({ name:"text", label:field.title, tree:isTree,
template: function(item){
if(moment(item[fname], moment.ISO_8601, true).isValid()) {
return moment(item[fname], moment.ISO_8601, true).format("MM/DD/YYYY h:mm:ss");
}
return item.text;
}});
}
else {
columns.push({ name:field.name, label:field.title, tree:isTree,
template: function(item){
if (!item.label) {
if(moment(item[fname], moment.ISO_8601, true).isValid()) {
return moment(item[fname], moment.ISO_8601, true).format("MM/DD/YYYY h:mm:ss");
}
return item[fname];
}
return "";
}
});
}
isTree = false;
});
columns.push({ name:"buttons", label:colHeader, width:30, template:colContent });
return columns;
}
function setChildTaskDisplay() {
function createBox(sizes, class_name){
var box = document.createElement('div');
box.style.cssText = [
"height:" + sizes.height + "px",
"line-height:" + sizes.height + "px",
"width:" + sizes.width + "px",
"top:" + sizes.top + 'px',
"left:" + sizes.left + "px",
"position:absolute"
].join(";");
box.className = class_name;
return box;
}
gantt.templates.grid_row_class = gantt.templates.task_class=function(start, end, task){
var css = [];
if(gantt.hasChild(task.id)){
css.push("task-parent");
}
if (!task.$open && gantt.hasChild(task.id)) {
css.push("task-collapsed");
}
if (task.$virtual || task.type == gantt.config.types.project)
css.push("summary-bar");
if(task.user_id){
css.push("gantt_resource_task gantt_resource_" + task.user_id);
}
return css.join(" ");
};
}
function ganttInit(){
gantt = main.dhx_gantt();
setScaleConfig("week");
gantt.templates.leftside_text = function(start, end, task){
if (!task.progress){
return "";
}
return "<span style='text-align:left;'>"+Math.round(task.progress*100)+ "% </span>";
};
gantt.config.step = 1;
gantt.config.duration_unit = "hour";
gantt.config.duration_step = 1;
gantt.config.scale_height = 75;
gantt.config.grid_width = 400;
gantt.config.fit_tasks = true;
gantt.config.columns = getGanttColumns();
gantt._onTaskIdChange = null;
gantt._onLinkIdChange = null;
gantt.config.autosize = "x";
gantt.config.grid_resize = true;
gantt.config.order_branch = true;
gantt.config.date_grid = "%d/%m/%Y %H %i";
gantt.serverList("users", []);
gantt.eachSuccessor = function(callback, root){
if(!this.isTaskExists(root))
return;
// remember tasks we've already iterated in order to avoid infinite loops
var traversedTasks = arguments[2] || {};
if(traversedTasks[root])
return;
traversedTasks[root] = true;
var rootTask = this.getTask(root);
var links = rootTask.$source;
if(links){
for(var i=0; i < links.length; i++){
var link = this.getLink(links[i]);
if(this.isTaskExists(link.target)){
callback.call(this, this.getTask(link.target));
// iterate the whole branch, not only first-level dependencies
this.eachSuccessor(callback, link.target, traversedTasks);
}
}
}
};
gantt.templates.task_class=function(start, end, task){
if(task.$virtual)
return "summary-bar";
};
ganttAttachEvents();
setChildTaskDisplay();
fetchRecords();
}
function ganttAttachEvents(){
gantt.templates.rightside_text = function(start, end, task){
return byId(gantt.serverList("users"), task.user_id);
};
if (schema.taskUser) {
gantt.attachEvent("onParse", function(){
var styleId = "dynamicGanttStyles";
var element = document.getElementById(styleId);
if(!element){
element = document.createElement("style");
element.id = styleId;
document.querySelector("head").appendChild(element);
}
var html = [".gantt_cell:nth-child(1) .gantt_tree_content{" +
" border-radius: 16px;" +
" width: 100%;" +
" height: 70%;" +
" margin: 5% 0;" +
" line-height: 230%;}"];
var resources = gantt.serverList("users");
resources.forEach(function(r){
html.push(".gantt_task_line.gantt_resource_" + r.key + "{" +
"background-color:"+r.backgroundColor+"; " +
"color:"+r.textColor+";" +
"}");
html.push(".gantt_row.gantt_resource_" + r.key + " .gantt_cell:nth-child(1) .gantt_tree_content{" +
"background-color:"+r.backgroundColor+"; " +
"color:"+r.textColor+";" +
"}");
});
element.innerHTML = html.join("");
});
}
gantt.attachEvent("onAfterTaskAdd", updateRecord);
gantt.attachEvent("onAfterTaskUpdate", updateRecord);
gantt.attachEvent("onAfterTaskDelete", scope.doRemove);
gantt.attachEvent("onAfterLinkAdd", updateLink);
gantt.attachEvent("onAfterLinkUpdate", updateLink);
gantt.attachEvent("onAfterLinkDelete", deleteLink);
gantt.attachEvent("onTaskCreated",function(task){
scope.showEditor(task, true);
return false;
});
gantt.attachEvent("onBeforeLightbox", function(id) {
var task = gantt.getTask(id);
scope.showEditor(task, false);
return false;
});
var diff = 0;
gantt.attachEvent("onBeforeTaskChanged", function(id, mode, originalTask){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
var modifiedTask = gantt.getTask(id);
diff = modifiedTask.start_date - originalTask.start_date;
}
return true;
});
//rounds positions of the child items to scale
gantt.attachEvent("onAfterTaskDrag", function(id, mode, e){
var modes = gantt.config.drag_mode;
if(mode == modes.move ){
gantt.eachSuccessor(function(child){
child.start_date = gantt.roundDate(new Date(child.start_date.valueOf() + diff));
child.end_date = gantt.calculateEndDate(child.start_date, child.duration);
gantt.updateTask(child.id);
},id );
}
});
}
function fetchRecords() {
scope.fetchItems(function(records) {
var data = [];
var links = [];
_.each(records, function(rec) {
addData(data, rec);
addLinks(links, rec);
});
gantt.parse({ "data":data, "links":links });
});
}
function updateRecordItem(id,link,toRemove){
var linkMap = {
"0":"finishToStart",
"1":"startToStart",
"2":"finishToFinish",
"3":"startToFinish"
};
var linkField = schema[linkMap[link.type]];
var task = gantt.getTask(link.target);
var record = task.record;
if(record && linkField){
var endRecord = gantt.getTask(link.source).record;
if(endRecord){
var recordList = record[linkField];
recordList = recordList.filter(function(item, idx) {
return item.id != endRecord.id;
});
if(!toRemove){
recordList.push(endRecord);
}
record[linkField] = recordList;
task.record = record;
scope.doSave(task, updateTaskRecord);
}
}
}
function updateLink(id,link){
updateRecordItem(id, link, false);
}
function updateTaskRecord(task, rec){
task.record = rec;
}
function deleteLink(id, link){
updateRecordItem(id, link, true);
}
function updateRecord(id, item){
var record = item.record;
if(!record){ record = {}; }
var duration = item.duration || 1;
record[schema.taskStart] = item.start_date.toJSON();
record[firstField.name] = item.text;
if(schema.taskProgress){
record[schema.taskProgress] = item.progress*100;
}
if(schema.taskSequence){
record[schema.taskSequence] = item.order;
}
if(schema.taskDuration){
record[schema.taskDuration] = duration;
}
if(schema.taskEnd){
record[schema.taskEnd] = item.end_date.toJSON();
}
if(schema.taskParent && item.parent && !record[schema.taskParent]){
var parentTask = gantt.getTask(item.parent);
var parentRecord = parentTask.record;
if(parentRecord){
record[schema.taskParent] = parentRecord;
}
}
return scope.doSave(item, updateTaskRecord);
}
function addData(data, rec){
if(rec[schema.taskStart]){
var dict = {
id:rec.id,
open:true,
isNew:true
};
dict = updateData(dict, rec);
dict.isNew = false;
if(dict.start_date){
data.push(dict);
}
}
}
function addLinkDict(links, targetRecordId, sourceRecords, linkType){
_.each(sourceRecords, function(sourceRec){
links.push({
id:targetRecordId+"-"+sourceRec.id,
target:targetRecordId,
source:sourceRec.id,
type:linkType
});
});
}
function addLinks(links,record){
var linkMap = {
"finishToStart":"0",
"startToStart":"1",
"finishToFinish":"2",
"startToFinish":"3"
};
_.each(_.keys(linkMap), function(key) {
if(schema[key]){
addLinkDict(links, record.id, record[schema[key]], linkMap[key]);
}
});
}
function updateTask(task, rec){
task = updateData(task, rec);
if(!task.isNew){
gantt.refreshTask(task.id);
}
task.isNew = false;
return task;
}
function updateData(task, rec){
task.record = rec;
var name = firstField.targetName ? rec[firstField.targetName] : rec[firstField.name];
task.text = "";
if(name){
task.text = name;
}
_.each(fields,function(field){
var val = rec[field.name];
if(_.isObject(val) && field.targetName){
val = val[field.targetName];
}
task[field.name] = val || "";
});
var endDate = null;
if(schema.taskEnd && rec[schema.taskEnd]){
endDate = moment(rec[schema.taskEnd]);
task.end_date = endDate.toDate();
if(task.isNew){
task.end_date = endDate.format("DD-MM-YYYY HH:mm:SS");
}
}
var startDate = moment(rec[schema.taskStart]);
if(task.isNew){
task.start_date = startDate.format("DD-MM-YYYY HH:mm:SS");
}
else{
task.start_date = startDate.toDate();
}
if(schema.taskDuration && rec[schema.taskDuration]){
task.duration = rec[schema.taskDuration];
}
else if(endDate){
task.duration = gantt.calculateDuration(startDate.toDate(), endDate);
}
else{
task.duration = "1";
}
if(!endDate){
task.end_date = gantt.calculateEndDate(startDate.toDate(), task.duration);
}
if(schema.taskProgress){
task.progress = rec[schema.taskProgress]/100;
}
if(schema.taskParent){
if(rec[schema.taskParent] && rec[schema.taskParent].id != task.id){
task.parent = rec[schema.taskParent].id;
}
else{
task.parent = 0;
}
}
if(schema.taskSequence){
task.sortorder = rec[schema.taskSequence];
}
if(schema.taskUser && rec[schema.taskUser]) {
task.user_id = rec[schema.taskUser].id;
if(!byId(gantt.serverList("users"), task.user_id)) {
gantt.serverList("users").push({key:task.user_id,
label:rec[schema.taskUser][fields[schema.taskUser].targetName],
backgroundColor: get_random_color(),
textColor:"#FFF"
});
}
}
return task;
}
function get_random_color() {
function c() {
var hex = Math.floor(Math.random()*256).toString(16);
return ("0"+String(hex)).substr(-2); // pad with zero
}
return "#"+c()+c()+c();
}
scope.onMode = function(name) {
setScaleConfig(name);
mode = name;
gantt.render();
};
scope.isMode = function(name) {
return mode === name;
};
scope.onRefresh = function () {
gantt.clearAll();
fetchRecords();
};
scope.onPrint = function () {
gantt.exportToPDF({
name: "Gantt.pdf",
callback: function(result){
window.open(result.url , '_self');
}});
};
scope.$on('$destroy', function() {
gantt.clearAll();
gantt.detachAllEvents();
});
scope.showEditor = function(task, isNew) {
var record = _.extend({}, task.record);
if (!editor) {
editor = ViewService.compile('<div ui-editor-popup></div>')(scope.$new());
editor.data('$target', element);
}
var popup = editor.isolateScope();
popup.setEditable(true);
if(isNew && schema.taskParent && task.parent && !record[schema.taskParent]){
var parentTask = gantt.getTask(task.parent);
var parentRecord = parentTask.record;
if(parentRecord){
record[schema.taskParent] = parentRecord;
}
}
popup.show(record, function(result) {
task.isNew = isNew;
task = updateTask(task, result);
if(isNew){
gantt.addTask(task);
}
else {
gantt.updateTask(task.id);
}
});
if (!record || !record.id) {
popup.waitForActions(function() {
popup.$broadcast("on:new");
});
}
};
}
return {
link:function(scope, element, attrs, controller) {
scope._viewPromise.then(function(){
link(scope, element, attrs, controller);
});
},
replace:true,
template:
'<div>'+
'<div class="gantt-main"></div>'+
'</div>'
};
}]);
})();

1065
sophal/js/view/view.grid.js Normal file

File diff suppressed because it is too large Load Diff

130
sophal/js/view/view.html.js Normal file
View File

@ -0,0 +1,130 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.HtmlViewCtrl = HtmlViewCtrl;
ui.HtmlViewCtrl.$inject = ['$scope', '$element', '$sce', '$interpolate'];
function HtmlViewCtrl($scope, $element, $sce, $interpolate) {
var views = $scope._views;
var stamp = -1;
$scope.view = views.html;
$scope.getContext = function () {
var params = $scope._viewParams || {};
var parent = $scope.$parent;
return _.extend({}, params.context, parent.getContext ? parent.getContext() : {});
};
$scope.getURL = function getURL() {
var view = $scope.view;
if (view) {
var url = view.name || view.resource;
if (stamp > 0) {
var q = url.lastIndexOf('?');
if (q > -1) {
url += "&t" + stamp;
} else {
url += "?t" + stamp;
}
}
if (url && url.indexOf('{{') > -1) {
url = $interpolate(url)($scope.getContext());
}
return $sce.trustAsResourceUrl(url);
}
return null;
};
$scope.show = function() {
$scope.updateRoute();
};
$scope.onRefresh = function () {
if (stamp > -1) {
stamp = new Date().getTime();
} else {
stamp = 0;
}
};
$scope.getRouteOptions = function() {
return {
mode: "html"
};
};
$scope.setRouteOptions = function(options) {
$scope.updateRoute();
};
if ($scope._viewParams) {
$scope._viewParams.$viewScope = $scope;
$scope.show();
}
$scope.$applyAsync(function() {
if ($scope.view.deferred) {
$scope.view.deferred.resolve($scope);
}
});
}
var directiveFn = function(){
return {
controller: HtmlViewCtrl,
replace: true,
link: function (scope, element) {
setTimeout(function () {
element.parents('[ui-attach]').each(function () {
$(this).scope().keepAttached = true;
});
}, 100);
// XXX: chrome 76 issue? See RM-20400
if (axelor.browser.chrome) {
scope.$on('on:nav-click', function (e, tab) {
if (tab.$viewScope !== scope) return;
var iframe = element.find('iframe')[0];
var embed = iframe.contentDocument.body.firstChild;
if (embed && embed.id === 'plugin') {
embed.height = '101%';
setTimeout(function () {
embed.height = '100%';
});
}
});
}
},
template:
'<div class="iframe-container">'+
'<iframe ng-src="{{getURL()}}" frameborder="0" scrolling="auto"></iframe>'+
'</div>'
};
};
ui.directive('uiViewHtml', directiveFn);
ui.directive('uiPortletHtml', directiveFn);
})();

View File

@ -0,0 +1,661 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
function BaseCardsCtrl(type, $scope, $element) {
ui.DSViewCtrl(type, $scope, $element);
$scope.getRouteOptions = function() {
return {
mode: type,
args: []
};
};
$scope.setRouteOptions = function(options) {
if (!$scope.isNested) {
$scope.updateRoute();
}
};
var ds = $scope._dataSource;
var initialized = false;
$scope.onShow = function (viewPromise) {
if (initialized) {
return $scope.onRefresh();
}
initialized = true;
viewPromise.then(function (meta) {
$scope.parse(meta.fields, meta.view);
});
};
$scope.parse = function (fields, view) {
};
$scope.onNew = function () {
ds._page.index = -1;
$scope.switchTo('form', function (formScope) {
formScope.edit(null);
formScope.setEditable();
formScope.$broadcast("on:new");
});
};
$scope.onRefresh = function () {
return $scope.filter({});
};
function update(records) {
$scope.records = records;
}
$scope.handleEmpty = function () {
};
$scope.filter = function(options) {
var view = $scope.schema;
var opts = {
fields: _.pluck($scope.fields, 'name')
};
var handleEmpty = $scope.handleEmpty.bind($scope);
if (options.criteria || options._domains) {
opts.filter = options;
}
if (options.archived !== undefined) {
opts.archived = options.archived;
}
if (view.orderBy) {
opts.sortBy = view.orderBy.split(',');
}
var promise = ds.search(opts);
promise.then(handleEmpty, handleEmpty);
return promise.success(update).then(function () {
$scope.handleEmpty();
return ds.fixPage();
});
};
$scope.pagerText = function() {
var page = ds._page;
if (page && page.from !== undefined) {
if (page.total === 0) return null;
return _t("{0} to {1} of {2}", page.from + 1, page.to, page.total);
}
};
$scope.onNext = function() {
var fields = _.pluck($scope.fields, 'name');
return ds.next(fields).success(update);
};
$scope.onPrev = function() {
var fields = _.pluck($scope.fields, 'name');
return ds.prev(fields).success(update);
};
$scope.getActionData = function(context) {
return _.extend({
_domain: ds._lastDomain,
_domainContext: _.extend({}, ds._lastContext, context),
_archived: ds._showArchived
}, ds._filter);
};
}
ui.controller("CardsCtrl", ['$scope', '$element', function CardsCtrl($scope, $element) {
BaseCardsCtrl.call(this, 'cards', $scope, $element);
$scope.viewItems = {};
$scope.parse = function (fields, view) {
var viewItems = {};
_.each(view.items, function (item) {
if (item.name) {
viewItems[item.name] = _.extend({}, item, fields[item.name], item.widgetAttrs);
}
});
$scope.viewItems = viewItems;
$scope.onRefresh();
$scope.waitForActions(axelor.$adjustSize);
};
$scope.onExport = function (full) {
var fields = full ? [] : _.pluck($scope.viewItems, 'name');
return $scope._dataSource.export_(fields).success(function(res) {
var fileName = res.fileName;
var filePath = 'ws/rest/' + $scope._model + '/export/' + fileName;
ui.download(filePath, fileName);
});
};
}]);
ui.controller("KanbanCtrl", ['$scope', '$element', 'ActionService', function KanbanCtrl($scope, $element, ActionService) {
BaseCardsCtrl.call(this, 'kanban', $scope, $element);
$scope.parse = function (fields, view) {
var params = $scope._viewParams.params || {};
var hideCols = (params['kanban-hide-columns'] || '').split(',');
var columnBy = fields[view.columnBy] || {};
var columns = _.filter(columnBy.selectionList, function (item) {
return hideCols.indexOf(item.value) === -1;
});
var first = _.first(columns);
if (view.onNew) {
first.canCreate = true;
}
var sequenceBy = fields[view.sequenceBy] || {};
if (["integer", "long"].indexOf(sequenceBy.type) === -1 || ["id", "version"].indexOf(sequenceBy.name) > -1) {
throw new Error("Invalid sequenceBy field in view: " + view.name);
}
$scope.sortableOptions.disabled = !view.draggable || !$scope.hasPermission('write');
$scope.columns = columns;
$scope.colWidth = params['kanban-column-width'];
};
$scope.move = function (record, to, next, prev) {
if(!record) {
return;
}
var ds = $scope._dataSource._new($scope._model);
var view = $scope.schema;
var rec = _.pick(record, "id", "version", view.sequenceBy);
var prv = prev ? _.pick(prev, "id", "version", view.sequenceBy) : null;
var nxt = next ? _.pick(next, "id", "version", view.sequenceBy) : null;
// update columnBy
rec[view.columnBy] = to;
// update sequenceBy
var all = _.compact([prv, rec, nxt]);
var offset = _.min(_.pluck(all, view.sequenceBy)) || 0;
_.each(all, function (item, i) {
item[view.sequenceBy] = offset + i;
});
function doSave() {
return ds.saveAll(all).success(function (records) {
_.each(_.compact([prev, rec, next]), function (item) {
_.extend(item, _.pick(ds.get(item.id), "version", view.sequenceBy));
});
_.extend(record, rec);
}).error(function () {
$scope.onRefresh();
});
}
if (view.onMove) {
var actScope = $scope.$new();
actScope.record = rec;
actScope.getContext = function () {
return _.extend({}, $scope._context, rec);
};
return ActionService.handler(actScope, $(), { action: view.onMove }).handle().then(function () {
return doSave();
}, function (err) {
$scope.onRefresh();
});
}
return doSave();
};
$scope.onRefresh = function () {
$scope.$broadcast("on:refresh");
};
$scope.filter = function(searchFilter) {
var options = {};
if (searchFilter.criteria || searchFilter._domains) {
options = {
filter: searchFilter
};
if (searchFilter.archived !== undefined) {
options.archived = searchFilter.archived;
}
$scope.$broadcast("on:filter", options);
}
};
$scope.sortableOptions = {
connectWith: ".kanban-card-list",
items: ".kanban-card",
tolerance: "pointer",
helper: "clone",
stop: function (event, ui) {
$scope.$broadcast('on:re-attach-click');
var item = ui.item;
var sortable = item.sortable;
var source = sortable.source.scope();
var target = (sortable.droptarget || $(this)).scope();
var next = item.next().scope();
var prev = item.prev().scope();
if (next) next = next.record;
if (prev) prev = prev.record;
var index = sortable.dropindex;
if (source === target && sortable.index === index) {
return;
}
$scope.move(target.records[index], target.column.value, next, prev);
$scope.$applyAsync();
}
};
}]);
ui.directive('uiKanbanColumn', ["ActionService", function (ActionService) {
return {
scope: true,
link: function (scope, element, attrs) {
var ds = scope._dataSource._new(scope._model);
var view = scope.schema;
var elemMore = element.children(".kanban-more");
ds._context = _.extend({}, scope._dataSource._context);
ds._context[view.columnBy] = scope.column.value;
ds._page.limit = view.limit || 20;
var domain = "self." + view.columnBy + " = :" + view.columnBy;
ds._domain = scope._dataSource._domain ? scope._dataSource._domain + " AND " + domain : domain;
scope.records = [];
function handleEmpty() {
element.toggleClass('empty', scope.isEmpty());
}
function fetch(options) {
var opts = _.extend({
offset: 0,
sortBy: [view.sequenceBy],
fields: _.pluck(scope.fields, 'name')
}, options);
elemMore.hide();
var promise = ds.search(opts);
promise.success(function (records) {
scope.records = scope.records.concat(records);
elemMore.fadeIn('slow');
});
return promise.then(handleEmpty, handleEmpty);
}
scope.$watch('records.length', handleEmpty);
scope.hasMore = function () {
var page = ds._page;
var next = page.from + page.limit;
return next < page.total;
};
scope.isEmpty = function () {
return scope.records.length == 0;
};
scope.onMore = function () {
var page = ds._page;
var next = scope.records.length;
if (next < page.total) {
return fetch({
offset: next
});
}
};
var onNew = null;
scope.getContext = function () {
var ctx = _.extend({}, scope._context);
ctx._value = scope.newItem;
return ctx;
};
scope.newItem = null;
scope.onCreate = function () {
var rec = scope.record = {};
var view = scope.schema;
rec[view.columnBy] = scope.column.value;
if (onNew === null) {
onNew = ActionService.handler(scope, element, {
action: view.onNew
});
}
var ds = scope._dataSource;
var promise = onNew.handle();
promise.then(function () {
ds.save(scope.record).success(function (rec) {
scope.newItem = null;
scope.records.unshift(rec);
});
});
};
scope.onEdit = function (record, readonly) {
scope.switchTo('form', function (formScope) {
formScope.edit(record);
formScope.setEditable(!readonly && scope.hasPermission('write') && formScope.canEdit());
});
};
scope.onDelete = function (record) {
axelor.dialogs.confirm(_t("Do you really want to delete the selected record?"),
function(confirmed) {
if (!confirmed) {
return;
}
ds.removeAll([record]).success(function(records, page) {
var index = scope.records.indexOf(record);
scope.records.splice(index, 1);
});
});
};
scope.$on("on:refresh", function (e) {
scope.newItem = null;
scope.records.length = 0;
fetch();
});
scope.$on("on:filter", function (e, options) {
scope.newItem = null;
scope.records.length = 0;
return fetch(options);
});
element.on("click", ".kanban-card", function (e) {
var elem = $(e.target);
var selector = '[ng-click],[ui-action-click],button,a,.iswitch,.ibox,.kanban-card-menu';
if (elem.is(selector) || element.find(selector).has(elem).length) {
return;
}
var record = $(this).scope().record;
scope.onEdit(record, true);
scope.$applyAsync();
});
if (scope.colWidth) {
element.width(scope.colWidth);
}
setTimeout(function () {
element.find('[ui-sortable]').sortable("option", "appendTo", element.parent());
});
fetch();
}
};
}]);
ui.directive('uiCards', function () {
return function (scope, element, attrs) {
var onRefresh = scope.onRefresh;
scope.onRefresh = function () {
scope.records = null;
return onRefresh.apply(scope, arguments);
};
scope.onEdit = function (record, readonly) {
var ds = scope._dataSource;
var page = ds._page;
page.index = record ? ds._data.indexOf(record) : -1;
scope.switchTo('form', function (formScope) {
formScope.setEditable(!readonly && scope.hasPermission('write') && formScope.canEdit());
});
};
scope.onDelete = function (record) {
axelor.dialogs.confirm(_t("Do you really want to delete the selected record?"),
function(confirmed) {
if (!confirmed) {
return;
}
var ds = scope._dataSource;
ds.removeAll([record]).success(function() {
scope.onRefresh();
});
});
};
scope.isEmpty = function () {
return (scope.records||[]).length == 0;
};
scope.handleEmpty = function () {
element.toggleClass('empty', scope.isEmpty());
};
};
});
ui.directive('uiCard', ["$compile", function ($compile) {
return {
scope: true,
link: function (scope, element, attrs) {
var body = element.find(".kanban-card-body");
var record = scope.record;
var evalScope = axelor.$evalScope(scope);
evalScope.record = record;
evalScope.getContext = scope.getContext = function () {
var ctx = _.extend({}, scope._context, scope.record);
ctx._model = scope._model;
return ctx;
};
if (!record.$processed) {
element.hide();
}
function process(record) {
if (record.$processed) {
return record;
}
record.$processed = true;
for (var name in record) {
if (!record.hasOwnProperty(name) || name.indexOf('.') === -1) {
continue;
}
var nested = record;
var names = name.split('.');
var head = _.first(names, names.length - 1);
var last = _.last(names);
var i, n;
for (i = 0; i < head.length; i++) {
n = head[i];
nested = nested[n] || (nested[n] = {});
}
nested[last] = record[name];
}
return record;
}
evalScope.$watch("record", function cardRecordWatch(record) {
_.extend(evalScope, process(record));
}, true);
evalScope.$image = function (fieldName, imageName) {
return ui.formatters.$image(scope, fieldName, imageName);
};
evalScope.$fmt = function (fieldName) {
return ui.formatters.$fmt(scope, fieldName, evalScope[fieldName]);
};
var template = (scope.schema.template || "<span></span>").trim();
if (template.indexOf('<') !== 0) {
template = "<span>" + template + "</span>";
}
scope.hilite = null;
$compile(template)(evalScope).appendTo(body);
var hilites = scope.schema.hilites || [];
for (var i = 0; i < hilites.length; i++) {
var hilite = hilites[i];
if (axelor.$eval(evalScope, hilite.condition, scope.record)) {
scope.hilite = hilite;
break;
}
}
if (scope.schema.width) {
element.parent().css("width", scope.schema.width);
}
if (scope.schema.minWidth) {
element.parent().css("min-width", scope.schema.minWidth);
}
if (scope.schema.maxWidth) {
element.parent().css("max-width", scope.schema.maxWidth);
}
function onClick(e) {
var elem = $(e.target);
var selector = '[ng-click],[ui-action-click],button,a,.iswitch,.ibox,.kanban-card-menu';
if (elem.is(selector) || element.find(selector).has(elem).length) {
return;
}
var record = $(this).scope().record;
scope.onEdit(record, true);
scope.$applyAsync();
}
function attachClick() {
element.on('click', onClick);
}
attachClick();
scope.$on('on:re-attach-click', function () {
element.off('click', onClick);
setTimeout(attachClick, 100);
});
element.fadeIn("slow");
var summaryHandler;
var summaryPlacement;
var summary = body.find('.card-summary.popover');
var configureSummary = _.once(function configureSummary() {
element.popover({
placement: function (tip, el) {
summaryPlacement = setTimeout(function () {
$(tip).css('visibility', 'hidden').css('max-width', 400).position({
my: 'left',
at: 'right',
of: el,
using: function (pos, feedback) {
$(feedback.element.element)
.css(pos)
.css('visibility', '')
.removeClass('left right')
.addClass(feedback.horizontal === 'left' ? 'right' : 'left');
summaryPlacement = null;
}
});
});
},
container: 'body',
trigger: 'manual',
title: summary.attr('title'),
content: summary.html(),
html: true
});
});
function showSummary() {
configureSummary();
if (summaryPlacement) {
clearTimeout(summaryPlacement);
summaryPlacement = null;
}
summaryHandler = setTimeout(function () {
summaryHandler = null;
element.popover('show');
}, 500);
}
function hideSummary() {
if (summaryPlacement) {
clearTimeout(summaryPlacement);
summaryPlacement = null;
}
if (summaryHandler) {
clearTimeout(summaryHandler);
summaryHandler = null;
}
element.popover('hide');
}
if (summary.length > 0) {
element.on('mouseenter.summary', showSummary);
element.on('mouseleave.summary', hideSummary);
element.on('mousedown.summary', hideSummary);
}
function destroy() {
if (summaryHandler) {
clearTimeout(summaryHandler);
summaryHandler = null;
}
if (element) {
element.off('mouseenter.summary');
element.off('mouseleave.summary');
element.off('mousedown.summary');
element.popover('destroy');
element = null;
}
}
element.on('$destroy', destroy);
scope.$on('$destroy', destroy);
}
};
}]);
})();

View File

@ -0,0 +1,511 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function () {
"use strict";
var ui = angular.module("axelor.ui");
EditorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService', '$q'];
function EditorCtrl($scope, $element, DataSource, ViewService, $q) {
var parent = $scope.$parent;
$scope._viewParams = parent._viewParams;
$scope.editorCanSave = parent.editorCanSave;
$scope.editorCanReload = parent.editorCanReload;
ui.ViewCtrl.call(this, $scope, DataSource, ViewService);
ui.FormViewCtrl.call(this, $scope, $element);
var closeCallback = null;
var originalEdit = $scope.edit;
var originalShow = $scope.show;
var recordVersion = -1;
var canClose = false;
var isClosed = true;
$scope.show = function(record, callback) {
originalShow();
if (_.isFunction(record)) {
callback = record;
record = null;
}
closeCallback = callback;
isClosed = false;
recordVersion = record ? record.version : -1;
if (recordVersion === undefined && record) {
recordVersion = record.$version;
}
this.edit(record);
};
function doEdit(record, fireOnLoad) {
if (record && record.id > 0 && (!_.isNumber(record.version) || !record.$fetched)) {
$scope.doRead(record.id).success(function(rec) {
if (record.$dirty) {
rec = _.extend({}, rec, record);
}
originalEdit(rec, fireOnLoad);
});
} else {
originalEdit(record, fireOnLoad);
}
canClose = false;
}
var parentCanEditTarget = null;
$scope.canEditTarget = function () {
if (parentCanEditTarget === null) {
var parent = $scope.$parent;
var func = parent.canEditTarget;
while (parent && func === $scope.canEditTarget) {
parent = parent.$parent;
func = parent.canEditTarget;
}
parentCanEditTarget = func || angular.noop;
}
return parentCanEditTarget() !== false;
};
var isEditable = $scope.isEditable;
$scope.isEditable = function () {
var id = ($scope.record || {}).id;
var perm = id > 0 ? 'write' : 'create';
if (parent.isReadonly && parent.isReadonly()) return false;
return $scope.hasPermission(perm)
&& (id > 0 ? $scope.canEditTarget() : true)
&& isEditable.call($scope);
};
var canEdit = $scope.canEdit;
$scope.canEdit = function() {
return $scope.canEditTarget() && canEdit.call($scope);
};
$scope.edit = function(record, fireOnLoad) {
if (isClosed) return;
$scope._viewPromise.then(function(){
doEdit(record, fireOnLoad);
$scope.setEditable(!$scope.$parent.$$readonly);
});
};
function isChanged() {
if ($scope.isDirty()) return true;
var record = $scope.record || {};
var version = record.version;
return recordVersion !== version || record.$forceDirty;
}
function canOK() {
if (isClosed) return false;
return isChanged();
}
function onOK() {
var record = $scope.record;
function close(value, forceSelect) {
if (value && (forceSelect || canOK())) {
value.$fetched = true;
value.selected = true;
$scope.$parent.select(value);
}
canClose = true;
$element.dialog('close');
if ($scope.editorCanReload) {
$scope.$parent.parentReload();
}
if (closeCallback && value) {
closeCallback(value);
}
closeCallback = null;
isClosed = true;
}
var event = $scope.$broadcast('on:before-save', record);
if (event.defaultPrevented) {
if (event.error) {
axelor.dialogs.error(event.error);
}
return;
}
// wait for onChange actions
$scope.waitForActions(function() {
if ($scope.editorCanSave && isChanged()) {
if (record.id < 0)
record.id = null;
return $scope.onSave({force: true}).then(function(record, page) {
// wait for onSave actions
$scope.waitForActions(function(){
close(record, true);
});
});
}
if ($scope.isValid()) {
close(record);
} else if ($scope.showErrorNotice) {
$scope.showErrorNotice();
}
}, 100);
}
$scope.onOK = function() {
$scope.$timeout(onOK, 10);
};
$scope.onBeforeClose = function(event, ui) {
if (canClose || !$scope.isDirty()) {
$scope.$evalAsync(function () {
$scope.edit(null, false);
});
return true;
}
event.preventDefault();
$scope.confirmDirty(function(){
canClose = true;
$element.dialog('close');
});
};
$scope.onHotKey = function (e, action) {
if (action === "save") {
$(e.target).blur().focus();
$scope.onOK();
}
$scope.$applyAsync();
return false;
};
}
SelectorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];
function SelectorCtrl($scope, $element, DataSource, ViewService) {
var parent = $scope.$parent;
$scope._viewParams = parent._viewParams;
$scope.getDomain = parent.getDomain;
ui.ViewCtrl.call(this, $scope, DataSource, ViewService);
ui.GridViewCtrl.call(this, $scope, $element);
var searchLimit = (parent.field||{}).searchLimit || 0;
if (searchLimit > 0) {
$scope._dataSource._page.limit = searchLimit;
}
function doFilter() {
$scope.filter($scope.getDomain());
}
var initialized = false;
var origShow = $scope.show;
$scope.show = function() {
origShow();
if (initialized) {
doFilter();
}
};
var _getContext = $scope.getContext;
$scope.getContext = function() {
// selector popup should return parent's context
if ($scope.$parent && $scope.$parent.getContext) {
return $scope.$parent.getContext();
}
return _getContext();
};
$scope.onItemClick = function(e, args) {
$scope.$applyAsync($scope.onOK.bind($scope));
};
var origOnShow = $scope.onShow;
$scope.onShow = function(viewPromise) {
viewPromise.then(function(){
var view = $scope.schema;
var field = $scope.field || $scope.$parent.field;
if (field) {
view.orderBy = field.orderBy || view.orderBy;
}
$element.dialog('open');
initialized = true;
origOnShow(viewPromise);
});
};
$scope.onOK = function() {
var selection = _.map($scope.selection, function(index){
return $scope.dataView.getItem(index);
});
if (!_.isEmpty(selection)) {
$scope.$applyAsync(function () {
$scope.$parent.select(selection);
$scope.selection = [];
});
}
$element.dialog('close');
};
$scope.onCreate = function () {
$element.dialog('close');
$scope.$parent.onNew();
};
$scope.canNew = function () {
return $scope.hasPermission('create') && $scope.$parent.canNew();
};
}
ui.directive('uiDialogSize', function() {
return function (scope, element, attrs) {
// use only with dialogs
if (attrs.uiDialog === undefined && !element.hasClass('ui-dialog-content')) {
return;
}
var loaded = false;
var addMaximizeButton = _.once(function () {
var elemDialog = element.parent();
var elemTitle = elemDialog.find('.ui-dialog-title');
var elemButton = $('<a href="#" class="ui-dialog-titlebar-max"><i class="fa fa-expand"></i></a>')
.click(function (e) {
$(this).children('i').toggleClass('fa-expand fa-compress');
elemDialog.toggleClass('maximized');
axelor.$adjustSize();
setTimeout(function () {
scope.$broadcast('grid:adjust-columns');
}, 350);
return false;
}).insertAfter(elemTitle);
// remove maximized state on close
element.on('dialogclose', function(e, ui) {
elemTitle.parent().find('i.fa-compress').toggleClass('fa-expand fa-compress');
elemDialog.removeClass('maximized');
});
var params = (scope._viewParams || {}).params || {};
if (params['popup.maximized']) {
elemButton.click();
}
});
var addCollapseButton = _.once(function () {
var elemDialog = element.parent();
var elemTitle = elemDialog.find('.ui-dialog-title');
$('<a href="#" class="ui-dialog-titlebar-collapse"><i class="fa fa-chevron-up"></i></a>')
.click(function (e) {
$(this).children('i').toggleClass('fa-chevron-up fa-chevron-down');
elemDialog.toggleClass('collapsed');
axelor.$adjustSize();
return false;
}).insertAfter(elemTitle);
// remove maximized and collapsed states on close
element.on('dialogclose', function(e, ui) {
elemTitle.parent().find('i.fa-compress').toggleClass('fa-expand fa-compress');
elemTitle.parent().find('i.fa-chevron-down').toggleClass('fa-chevron-down fa-chevron-up');
elemDialog.removeClass('maximized collapsed');
});
});
function doAdjust() {
element.dialog('open');
element.scrollTop(0);
setTimeout(doFocus);
if (scope._afterPopupShow) {
scope._afterPopupShow();
}
}
function doShow() {
addMaximizeButton();
addCollapseButton();
if (loaded) {
return setTimeout(doAdjust);
}
loaded = true;
scope.waitForActions(doAdjust);
}
function doFocus() {
var container = element.is('[ui-selector-popup]')
? element.find('.slick-headerrow')
: element;
var focusElem = container.find('input:tabbable');
if (focusElem.length == 0) {
focusElem = element.parent().find('.ui-dialog-buttonset').find(':tabbable');
}
if (focusElem[0]) {
focusElem[0].focus();
}
//XXX: ui-dialog issue
element.find('.slick-headerrow-column,.slickgrid,[ui-embedded-editor]').zIndex(element.zIndex());
element.find('.record-toolbar .btn').zIndex(element.zIndex()+1);
}
// a flag used by evalScope to detect popup (see form.base.js)
scope._isPopup = true;
scope._doShow = function(viewPromise) {
if (viewPromise && viewPromise.then) {
viewPromise.then(doShow);
} else {
doShow();
}
};
scope._setTitle = function (title) {
if (title) {
element.closest('.ui-dialog').find('.ui-dialog-title').text(title);
}
};
scope.adjustSize = function() {
};
};
});
ui.directive('uiEditorPopup', function() {
return {
restrict: 'EA',
controller: EditorCtrl,
scope: {},
link: function(scope, element, attrs) {
scope.onShow = function(viewPromise) {
scope._doShow(viewPromise);
};
scope.$watch('schema.title', function popupTitleWatch(title) {
scope._setTitle(title);
});
element.scroll(function (e) {
$(document).trigger('adjust:scroll', element);
});
var onNewHandler = scope.onNewHandler;
scope.onNewHandler = function (event) {
if (scope.isPopupOpen) {
return onNewHandler.apply(scope, arguments);
}
};
scope.isPopupOpen = true;
setTimeout(function () {
var isOpen = false;
element.on('dialogclose', function (e) {
isOpen = false;
scope.waitForActions(function () {
scope.isPopupOpen = isOpen;
scope.$$popupStack.pop(1);
}, 2000); // delay couple of seconds to that popup can cleanup
});
element.on('dialogopen', function (e) {
scope.isPopupOpen = isOpen = true;
scope.$$popupStack.push(1);
scope.$applyAsync();
});
});
},
replace: true,
template:
'<div ui-dialog ui-dialog-size x-on-ok="onOK" x-on-before-close="onBeforeClose" ui-watch-if="isPopupOpen">'+
'<div ui-view-form x-handler="this"></div>'+
'</div>'
};
});
ui.directive('uiSelectorPopup', function(){
return {
restrict: 'EA',
controller: SelectorCtrl,
scope: {
selectMode: "@"
},
link: function(scope, element, attrs) {
var onShow = scope.onShow;
scope.onShow = function (viewPromise) {
if (scope.clearFilters) {
scope.clearFilters();
scope.selection = [];
}
onShow(viewPromise);
scope._doShow(viewPromise);
};
scope.$watch('schema.title', function popupTitleWatch(title){
scope._setTitle(title);
});
var btnOK = null;
function buttonState(count) {
if (btnOK === null) {
btnOK = element.siblings('.ui-dialog-buttonpane').find('.btn:last');
}
return btnOK.attr('disabled', !count || count <= 0);
}
scope.$watch('selection.length', buttonState);
setTimeout(function(){
var footer = element.closest('.ui-dialog').find('.ui-dialog-buttonpane'),
header = element.closest('.ui-dialog').find('.ui-dialog-titlebar'),
pager = element.find('.record-pager'),
buttons = element.find('.ui-dialog-buttonset-left');
header.find('.ui-dialog-title').after(pager);
footer.prepend(buttons);
footer.find('.button-ok').html(_t("Select"));
});
},
replace: true,
template:
'<div ui-dialog ui-dialog-size x-on-ok="onOK">'+
'<div ui-view-grid x-view="schema" x-data-view="dataView" x-handler="this" x-editable="false" x-selector="{{selectMode}}"></div>'+
'<div ui-record-pager></div>'+
'<div class="ui-dialog-buttonset-left pull-left" ng-show="canNew()">'+
'<button class="btn" ng-click="onCreate()" x-translate>Create</button>'+
'</div>'+
'</div>'
};
});
})();

View File

@ -0,0 +1,391 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
PortalCtrl.$inject = ['$scope', '$element'];
function PortalCtrl($scope, $element) {
var view = $scope._views.portal;
if (view.items) {
$scope.$timeout(function () {
$scope.parse(view);
});
} else {
$scope.loadView('portal', view.name).success(function(fields, schema){
$scope.parse(schema);
});
}
$scope.$applyAsync(function(){
if (view.deferred)
view.deferred.resolve($scope);
});
$scope.parse = function(schema) {
};
$scope.show = function(promise) {
$scope.updateRoute();
};
$scope.onShow = function() {
};
$scope.getContext = function() {
return _.extend({}, $scope._context);
};
$scope.getRouteOptions = function() {
return {
mode: 'portal',
args: []
};
};
$scope.setRouteOptions = function(options) {
if (!$scope.isNested) {
$scope.updateRoute();
}
};
}
var tmplPortlet =
'<div ui-view-portlet '+
'x-action="{{p.action}}" '+
'x-can-search="{{p.canSearch}}" '+
'x-col-span="{{p.colSpan}}" '+
'x-row-span="{{p.rowSpan}}" ' +
'x-height="{{p.height}}"></div>';
var tmplTabs =
"<div ui-portal-tabs x-schema='p'></div>";
ui.directive('uiViewPortal', ['$compile', function($compile) {
return {
scope: true,
controller: PortalCtrl,
link: function(scope, element, attrs) {
function init() {
element.sortable({
handle: ".portlet-header",
items: "> .portlet, > .portal-tabs",
forceHelperSize: true,
forcePlaceholderSizeType: true,
activate2: function(event, ui) {
var width = ui.placeholder.width();
var height = ui.placeholder.height();
ui.placeholder.width(width - 4);
ui.placeholder.height(height - 4);
ui.placeholder.css({
'left': '2px',
'top': '2px',
'margin-right': '4px'
});
},
deactivate: function(event, ui) {
axelor.$adjustSize();
}
});
}
scope.parse = function(schema) {
scope.portletCols = schema.cols || 2;
scope.portlets = schema.items;
_.each(scope.portlets, function (item) {
var tmpl = item.type === 'tabs' ? tmplTabs : tmplPortlet;
var child = scope.$new();
child.p = item;
var elem = $compile(tmpl)(child);
element.append(elem);
});
setTimeout(init);
};
},
replace: true,
transclude: true,
template: '<div class="portal" ng-transclude></div>'
};
}]);
PortletCtrl.$inject = ['$scope', '$element', 'MenuService', 'DataSource', 'ViewService'];
function PortletCtrl($scope, $element, MenuService, DataSource, ViewService) {
var self = this;
function init() {
ui.ViewCtrl.call(self, $scope, DataSource, ViewService);
$scope.show = function() {
};
$scope.onShow = function() {
};
}
$scope.initPortlet = function(action, options) {
MenuService.action(action, options).success(function(result){
if (_.isEmpty(result.data)) {
return;
}
var view = result.data[0].view;
$scope._viewParams = view;
$scope._viewAction = action;
init();
$scope.title = view.title;
$scope.parsePortlet(view);
});
};
$scope.$on('on:attrs-change:refresh', function(e) {
e.preventDefault();
if ($scope.onRefresh) {
$scope.onRefresh();
}
});
$scope.$on('on:tab-reload', function(e) {
if ($scope.onRefresh) {
$scope.onRefresh();
}
});
}
function setPortletSize(scope, element, attrs) {
var cols = scope.portletCols;
var colSpan = +attrs.colSpan || 1;
var rowSpan = +attrs.rowSpan || 1;
var width = 100;
var height = (+attrs.height || 250) * rowSpan;
width = (width / cols) * colSpan;
element.width(width + '%').height(height);
}
ui.directive('uiViewPortlet', ['$compile', function($compile){
return {
scope: true,
controller: PortletCtrl,
link: function(scope, element, attrs) {
var lazy = true;
(function () {
var counter = 0;
return function checkLoading() {
scope.waitForActions(function () {
if (counter < 10 && element.parent().is(":hidden")) {
counter++;
return setTimeout(checkLoading, 100);
}
lazy = !element.parent().is(".portal");
var unwatch = scope.$watch(function portalVisibleWatch() {
var action = attrs.action;
if (!action) {
return;
}
if (element.parent().is(":hidden")) {
lazy = true;
return;
}
unwatch();
unwatch = null;
var ctx;
if (scope.getContext) {
ctx = scope.getContext();
}
scope.initPortlet(action, {
context: ctx
});
});
});
};
})()();
scope.parsePortlet = _.once(function(view) {
scope.noFilter = attrs.canSearch != "true";
var template = $compile($('<div ui-portlet-' + view.viewType + '></div>'))(scope);
element.find('.portlet-content:first').append(template);
scope.show();
if (scope.portletCols) {
setPortletSize(scope, element, attrs);
}
// if lazy, load data
if (scope.onRefresh && lazy) {
scope.onRefresh();
}
});
scope.onPortletToggle = function(event) {
var e = $(event.target);
e.toggleClass('fa-chevron-up fa-chevron-down');
element.toggleClass('portlet-minimized');
if (e.hasClass('fa-chevron-up')) {
axelor.$adjustSize();
}
};
scope.doNext = function() {
if (this.canNext()) this.onNext();
};
scope.doPrev = function() {
if (this.canPrev()) this.onPrev();
};
},
replace: true,
template:
'<div class="portlet">'+
'<div class="portlet-body stackbar">'+
'<div class="portlet-header navbar">'+
'<div class="navbar-inner">'+
'<div class="container-fluid">'+
'<span class="brand" ng-bind-html="title"></span>'+
'<ul class="nav pull-right">'+
'<li class="portlet-pager" ng-show="showPager">'+
'<span class="portlet-pager-text">{{pagerText()}}</span>'+
'<span class="icons-bar">'+
'<i ng-click="doPrev()" ng-class="{disabled: !canPrev()}" class="fa fa-step-backward"></i>'+
'<i ng-click="doNext()" ng-class="{disabled: !canNext()}" class="fa fa-step-forward"></i>'+
'</span>'+
'</li>'+
'<li class="divider-vertical"></li>'+
'<li>'+
'<span class="icons-bar">'+
'<i title="{{\'Refresh\' | t}}" ng-click="onRefresh()" class="fa fa-refresh"></i>'+
'<i title="{{\'Toggle\' | t}}" ng-click="onPortletToggle($event)" class="fa fa-chevron-up"></i>'+
'</span>'+
'</li>'+
'</ul>'+
'</div>'+
'</div>'+
'</div>'+
'<div class="portlet-content"></div>'+
'</div>'+
'</div>'
};
}]);
ui.directive('uiPortalTabs', function() {
return {
scope: {
schema: '='
},
replace: true,
link: function(scope, element, attrs) {
var schema = scope.schema;
var first = _.first(schema.tabs);
if (first) {
first.active = true;
}
scope.tabClick = function (tab) {
_.each(schema.tabs, function (item) {
item.active = false;
});
tab.active = true;
axelor.$adjustSize();
};
scope.tabs = schema.tabs;
scope.portletCols = scope.$parent.portletCols;
setPortletSize(scope, element, {
colSpan: schema.colSpan,
rowSpan: schema.rowSpan,
height: schema.height
});
element.height('auto');
},
template:
"<div class='tabbable-tabs portal-tabs'>" +
"<ul class='nav nav-tabs nav-tabs-scrollable'>" +
"<li ng-repeat='tab in tabs' ng-class='{active: tab.active}'>" +
"<a href='' ng-click='tabClick(tab)' >{{tab.title}}</a>" +
"</li>" +
"</ul>" +
"<div class='tab-content portal-tab-content'>" +
"<div ng-repeat='tab in tabs' ng-class='{active: tab.active}' class='tab-pane'>" +
"<div ui-portal-tab x-schema='tab'></div>" +
"</div>" +
"</div>" +
"</div>"
};
});
ui.directive('uiPortalTab', function() {
return {
scope: {
schema: '='
},
controller: ['$scope', 'DataSource', 'ViewService', function ($scope, DataSource, ViewService) {
var view = $scope.schema;
var params = {
viewType: 'portal',
views: [ view ]
};
view.type = 'portal';
$scope._viewParams = params;
$scope.isNested = true;
$scope._model = null;
ui.ViewCtrl.apply(this, arguments);
}],
template: "<div ui-view-portal></div>"
};
});
})();

View File

@ -0,0 +1,856 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.controller('SearchViewCtrl', SearchViewCtrl);
SearchViewCtrl.$inject = ['$scope', '$element', '$http', 'DataSource', 'ViewService', 'MenuService'];
function SearchViewCtrl($scope, $element, $http, DataSource, ViewService, MenuService) {
var view = $scope._views.search || {};
$scope._dataSource = DataSource.create('multi-search');
$scope.$applyAsync(function(){
if (view.deferred)
view.deferred.resolve($scope);
});
function fixFields(fields) {
_.each(fields, function(field){
if (field.type == 'reference') {
field.type = 'MANY_TO_ONE';
field.canNew = false;
field.canEdit = false;
}
if (field.type)
field.type = field.type.toUpperCase();
else
field.type = 'STRING';
});
return fields;
}
$scope.show = function(viewPromise) {
if (!viewPromise) {
viewPromise = $scope.loadView('search', view.name);
viewPromise.success(function(fields, schema){
$scope.initView(schema);
});
}
$scope.onShow(viewPromise);
};
$scope.onShow = function() {
};
$scope.initView = function(schema) {
var params = $scope._viewParams;
$scope._searchFields = fixFields(schema.searchFields);
$scope._resultFields = fixFields(schema.resultFields);
$scope._searchView = schema;
$scope._showSingle = params.params && params.params.showSingle;
$scope._forceEdit = params.params && params.params.forceEdit;
$scope._hideActions = params.params && params.params.hideActions;
$scope.updateRoute();
if (params.options && params.options.mode == "search") {
$scope.setRouteOptions(params.options);
}
};
$scope.getRouteOptions = function() {
var args = [],
query = $scope._routeSearch;
return {
mode: 'search',
args: args,
query: query
};
};
$scope._routeSearch = null;
var onNewCalled = false;
$scope.setRouteOptions = function(options) {
var opts = options || {},
fields = $scope._searchFields || [],
search = opts.search,
record = {};
var changed = !angular.equals($scope._routeSearch, search);
$scope._routeSearch = search;
if (!onNewCalled && _.isEmpty(search)) {
onNewCalled = true;
scopes.form.$broadcast('on:new');
}
if (!search || _.isEmpty(search) || !changed) {
return $scope.updateRoute();
}
_.each(fields, function(field) {
var value = search[field.name];
if (value === undefined) {
return;
}
if (field.target) {
if (value) {
record[field.name] = {id: +value};
}
} else {
record[field.name] = value;
}
});
if (search.objects) {
scopes.toolbar.editRecord({
objectSelect: search.objects
});
}
scopes.form.editSearch(record, fields);
function _doSearch() {
var promise = $scope.doSearch();
if (promise && promise.then && $scope._showSingle) {
promise.then(function () {
var items = scopes.grid.getItems();
if (items && items.length === 1) {
scopes.grid.selection = [0];
scopes.grid.onEdit();
}
});
}
}
var promise = scopes.toolbar._viewPromise;
if (promise && promise.then) {
promise.then(function() {
$scope.$timeout(_doSearch);
});
} else {
_doSearch();
}
};
var scopes = {};
$scope._register = function(key, scope) {
scopes[key] = scope;
};
$scope.doSearch = function() {
var params = _.extend({}, scopes.form.record),
empty = _.chain(params).values().compact().isEmpty().value();
if (empty)
return $scope.doClear();
var selected = (scopes.toolbar.record || {}).objectSelect;
_.extend(params,{
__name: view.name,
__selected: _.isEmpty(selected) ? null : selected.split(/,\s*/)
});
var promise = $http.post('ws/search', {
limit: view.limit || 80,
data: params
});
return promise.then(function(response){
var res = response.data,
records = res.data || [];
// slickgrid expects unique `id` so generate them and store original one
_.each(records, function(rec, i){
rec._id = rec.id;
rec.id = i + 1;
});
scopes.grid.setItems(records);
if (scopes.form.$events.onLoad) {
scopes.form.record._count = records.length;
scopes.form.record._countByModels = _.countBy(records, function(rec) {
return rec._model;
});
scopes.form.$events.onLoad();
}
if (_.isEmpty(records)) {
axelor.notify.info(_t("No records found."));
}
});
};
$scope.doClear = function(all) {
scopes.form.edit(null);
scopes.form.$broadcast('on:new');
scopes.grid.setItems([]);
if (all) {
scopes.toolbar.edit(null);
scopes.toolbar.doReset();
}
};
$scope.doAction = function() {
var action = scopes.toolbar.getMenuAction();
if (!action) {
return;
}
var grid = scopes.grid,
index = _.first(grid.selection),
record = grid.getItem(index);
action = action.action;
record = _.extend({
_action: action
}, record);
record.id = record._id;
MenuService.action(action).success(function(result){
if (!result.data) {
return;
}
var view = result.data[0].view;
var tab = view;
tab.action = _.uniqueId('$act');
tab.viewType = 'form';
tab.context = _.extend({}, tab.context, {
_ref : record
});
$scope.openTab(tab);
});
};
}
ui.controller('SearchFormCtrl', SearchFormCtrl);
SearchFormCtrl.$inject = ['$scope', '$element', 'ViewService'];
function SearchFormCtrl($scope, $element, ViewService) {
ui.FormViewCtrl.call(this, $scope, $element);
$scope._register('form', $scope);
$scope.setEditable();
// prevent requesting defaults
$scope.defaultValues = {};
$scope.$watch('_searchView', function searchSchemaWatch(schema) {
if (!schema) return;
var form = {
title: 'Search',
type: 'form',
cols: 1,
items: [{
type: 'panel',
title: schema.title,
items: schema.searchFields
}]
};
var meta = { fields: schema.searchFields };
ViewService.process(meta, schema.searchForm);
function process(item) {
if (item.items || item.pages) {
return _.each(item.items || item.pages, process);
}
switch (item.widget) {
case 'ManyToOne':
case 'OneToOne':
case 'SuggestBox':
item.canNew = false;
item.canEdit = false;
break;
case 'OneToMany':
case 'ManyToMany':
case 'MasterDetail':
item.hidden = true;
}
}
if (schema.searchForm && schema.searchForm.items) {
_.each(schema.searchForm.items, process);
}
$scope.fields = meta.fields;
$scope.schema = schema.searchForm || form;
$scope.schema.loaded = true;
});
var model = null;
var getContext = $scope.getContext;
$scope.getContext = function() {
var view = $scope._searchView || {};
if (model === null && view.selects) {
model = (_.first(view.selects) || {}).model;
}
var ctx = getContext.apply(this, arguments) || {};
ctx._model = model;
return ctx;
};
$scope.editSearch = function (record, fields) {
$scope.editRecord(record);
setTimeout(function () {
_.each(fields, function (field) {
if (!field.target || !$scope.record) return;
var item = $element.find('[x-field=' + field.name + ']');
var itemScope = item.data('$scope');
var value = itemScope.getValue();
if (value && itemScope && !itemScope.text && itemScope.select) {
itemScope.select(value);
}
});
});
};
}
ui.controller('SearchGridCtrl', SearchGridCtrl);
SearchGridCtrl.$inject = ['$scope', '$element', 'ViewService', '$interpolate'];
function SearchGridCtrl($scope, $element, ViewService, $interpolate) {
ui.GridViewCtrl.call(this, $scope, $element);
$scope._register('grid', $scope);
var viewTitles = {};
$scope.$watch('_searchView', function searchSchemaWatch(schema) {
if (!schema) return;
var view = {
title: 'Search',
type: 'grid',
editIcon: true,
items: []
};
var objItem = _.findWhere(schema.resultFields, {name: 'object'});
if (!objItem) {
view.items.push(objItem = {});
}
objItem = _.extend(objItem, { name : '_modelTitle', title: _t('Object') });
view.items = view.items.concat(schema.resultFields);
if (+(objItem.width) === 0) {
objItem.hidden = true;
}
var meta = { fields: schema.resultFields };
ViewService.process(meta);
_.each(schema.selects, function (select) {
viewTitles[select.model] = select.viewTitle;
});
_.each(view.items, function (item) {
if (item.width) {
objItem.width = objItem.width || 220;
}
});
$scope.fields = meta.fields;
$scope.schema = view;
$scope.schema.loaded = true;
});
$scope.onEdit = function(force) {
var index = _.first(this.selection),
records = this.getItems(),
record = this.getItem(index),
ids, domain, views;
ids = _.chain(records).filter(function(rec){
return rec._model == record._model;
}).pluck('_id').value();
domain = "self.id IN (" + ids.join(',') + ")";
views = _.map(['form', 'grid'], function(type){
var view = { type : type };
var name = record["_" + type];
if (name) view.name = name;
return view;
});
if (force === undefined) {
force = $scope._forceEdit;
}
var title = viewTitles[record._model];
if (title) {
title = $interpolate(title)(record);
}
var tab = {
action: _.uniqueId('$act'),
model: record._model,
title: title || record._modelTitle,
forceTitle: true,
domain: domain,
recordId: record._id,
forceEdit: force,
viewType: 'form',
views: views
};
this.openTab(tab);
};
$scope.onSort = function(event, args) {
var grid = args.grid;
var data = grid.getData();
var sortCols = args.sortCols;
var types = {};
_.each($scope.fields, function (field) {
types[field.name] = field.type;
});
data.sort(function(dataRow1, dataRow2) {
for (var i = 0, l = sortCols.length; i < l; i++) {
var name = sortCols[i].sortCol.field;
var sign = sortCols[i].sortAsc ? 1 : -1;
var value1 = dataRow1[name], value2 = dataRow2[name];
switch (types[name]) {
case "integer":
case "long":
value1 = value1 || 0;
value2 = value2 || 0;
break;
default:
value1 = value1 || "";
value2 = value2 || "";
}
var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
if (result) {
return result;
}
}
return 0;
});
grid.invalidate();
grid.render();
};
}
ui.controller('SearchToolbarCtrl', SearchToolbarCtrl);
SearchToolbarCtrl.$inject = ['$scope', '$element', '$http'];
function SearchToolbarCtrl($scope, $element, $http) {
ui.FormViewCtrl.call(this, $scope, $element);
$scope._register('toolbar', $scope);
$scope.setEditable();
var menus = {};
function fetch(key, parent, request, response) {
if (menus[key]) {
return response(menus[key]);
}
var promise = $http.get('ws/search/menu', {
params: {
parent: parent
}
});
promise.then(function(res){
var data = res.data.data;
data = _.map(data, function(item){
return {
value: item.name,
action: item.action,
label: item.title
};
});
menus[key] = data;
response(data);
});
}
$scope.fetchRootMenus = function(request, response) {
fetch('menuRoot', null, request, response);
};
$scope.fetchSubMenus = function(request, response) {
fetch('menuSub', $scope.record.menuRoot, request, response);
};
$scope.fetchItemMenus = function(request, response) {
fetch('menuItem', $scope.record.menuSub, request, response);
};
$scope.resetSelector = function(a, b) {
_.each(arguments, function(name){
if (_.isString(name)) {
$scope.record[name] = null;
menus[name] = null;
}
});
};
$scope.getMenuAction = function() {
return _.find(menus.menuItem, function(item){
return item.value === $scope.record.menuItem;
});
};
$scope.$watch('_searchView', function searchSchemaWatch(schema) {
if (!schema) {
return;
}
var selected = [];
$scope.fields = {
'objectSelect' : {
type : 'string',
placeholder: _t('Search Objects'),
multiple : true,
selectionList : _.map(schema.selects, function(x) {
if (x.selected) {
selected.push(x.model);
}
return {
value : x.model,
title : x.title
};
})
},
'menuRoot' : {
type : 'string',
placeholder: _t('Action Category'),
widget: 'select-query',
attrs: {
query: 'fetchRootMenus',
'ng-change': 'resetSelector("menuSub", "menuItem")'
}
},
'menuSub' : {
placeholder: _t('Action Sub-Category'),
widget: 'select-query',
attrs: {
query: 'fetchSubMenus',
'ng-change': 'resetSelector($event, "menuItem")'
}
},
'menuItem' : {
placeholder: _t('Action'),
widget: 'select-query',
attrs: {
query: 'fetchItemMenus'
}
}
};
var items1 = [{
name : 'objectSelect',
showTitle : false,
colSpan: 8
}, {
type : 'button',
title : _t('Search'),
colSpan: 2,
attrs: {
'ng-click': 'doSearch()'
}
}, {
type : 'button',
title : _t('Clear'),
colSpan: 2,
attrs: {
'ng-click': 'doClear(true)'
}
}];
var items2 = [{
name : 'menuRoot',
showTitle : false,
colSpan: 3
}, {
name : 'menuSub',
showTitle : false,
colSpan: 3
}, {
name : 'menuItem',
showTitle : false,
colSpan: 4
}, {
type : 'button',
title : _t('Go'),
colSpan: 2,
attrs: {
'ng-click': 'doAction()'
}
}];
var item1 = {
type: "panel",
colSpan: $scope._hideActions ? 12 : 6,
items: items1
};
var item2 = {
type: "panel",
colSpan: 6,
items: items2
};
schema = {
type : 'form',
items: [{
type: 'panel',
items: $scope._hideActions ? [item1] : [item1, item2]
}]
};
$scope.schema = schema;
$scope.schema.loaded = true;
$scope.doReset = function () {
var record = $scope.record || {};
if (selected.length > 0 && _.isEmpty(record.objectSelect)) {
record.objectSelect = selected.join(', ');
$scope.edit(record);
}
};
$scope.$timeout($scope.doReset);
});
}
angular.module('axelor.ui').directive('uiViewSearch', function(){
return {
controller: SearchViewCtrl,
link: function(scope, element, attrs, ctrl) {
element.on('keypress', '.search-view-form form:first', function(event){
if (event.keyCode == 13 && $(event.target).is('input')){
scope.doSearch();
}
});
var grid = element.children('.search-view-grid');
scope.$onAdjust(function(){
if (!element.is(':visible'))
return;
grid.height(element.height() - grid.position().top);
});
}
};
});
// ActionSelector (TODO: re-use search view toolbar)
ActionSelectorCtrl.$inject = ['$scope', '$element', '$attrs', '$http', 'MenuService'];
function ActionSelectorCtrl($scope, $element, $attrs, $http, MenuService) {
ui.FormViewCtrl.call(this, $scope, $element);
var menus = {},
category = $attrs.category;
function fetch(key, request, response, params) {
if (menus[key]) {
return response(menus[key]);
}
var promise = $http.get('ws/search/menu', {
params: params
});
promise.then(function(res){
var data = res.data.data;
data = _.map(data, function(item){
return {
value: item.name,
action: item.action,
label: item.title
};
});
menus[key] = data;
response(data);
});
}
$scope.fetchRootMenus = function(request, response) {
fetch('$menuRoot', request, response, {
parent: '',
category: category
});
};
$scope.fetchSubMenus = function(request, response) {
if (!$scope.record.$menuRoot) return;
fetch('$menuSub', request, response, {
parent: $scope.record.$menuRoot
});
};
$scope.fetchItemMenus = function(request, response) {
if (!$scope.record.$menuSub) return;
fetch('$menuItem', request, response, {
parent: $scope.record.$menuSub
});
};
$scope.resetSelector = function(a, b) {
_.each(arguments, function(name){
if (_.isString(name)) {
$scope.record[name] = null;
menus[name] = null;
}
});
};
$scope.getMenuAction = function() {
return _.find(menus.$menuItem, function(item){
return item.value === $scope.record.$menuItem;
});
};
$scope.doAction = function() {
var action = $scope.getMenuAction();
if (!action) {
return;
}
var context = $scope.$parent.getContext(),
record;
action = action.action;
record = {
id : context.id,
_action: action,
_model: context._model
};
MenuService.action(action).success(function(result){
if (!result.data) {
return;
}
var view = result.data[0].view;
var tab = view;
tab.action = _.uniqueId('$act');
tab.viewType = 'form';
tab.context = _.extend({}, tab.context, {
_ref : record
});
$scope.openTab(tab);
});
};
$scope.fields = {
'$menuRoot' : {
type : 'string',
placeholder: _t('Action Category'),
widget: 'select-query',
attrs: {
query: 'fetchRootMenus',
'ng-change': 'resetSelector("$menuSub", "$menuItem")'
}
},
'$menuSub' : {
placeholder: _t('Action Sub-Category'),
type: 'select-query',
attrs: {
query: 'fetchSubMenus',
'ng-change': 'resetSelector($event, "$menuItem")'
}
},
'$menuItem' : {
placeholder: _t('Action'),
widget: 'select-query',
attrs: {
query: 'fetchItemMenus'
}
}
};
$scope.schema = {
cols : 4,
colWidths : '30%,30%,30%,10%',
type : 'form',
items : [ {
name : '$menuRoot',
showTitle : false
}, {
name : '$menuSub',
showTitle : false
}, {
name : '$menuItem',
showTitle : false
}, {
type : 'button',
title : _t('Go'),
attrs: {
'ng-click': 'doAction()'
}
} ]
};
}
angular.module('axelor.ui').directive('uiActionSelector', function(){
return {
scope: true,
controller: ActionSelectorCtrl,
template: '<div ui-view-form x-handler="this"></div>'
};
});
})();

795
sophal/js/view/view.tree.js Normal file
View File

@ -0,0 +1,795 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.controller('TreeViewCtrl', TreeViewCtrl);
TreeViewCtrl.$inject = ['$scope', '$element', 'DataSource', 'ActionService'];
function TreeViewCtrl($scope, $element, DataSource, ActionService) {
var view = $scope._views.tree;
var viewPromise = $scope.loadView('tree', view.name);
$scope.$applyAsync(function() {
if (view.deferred) {
view.deferred.resolve($scope);
}
});
viewPromise.success(function(fields, schema){
$scope.parse(schema);
});
$scope.show = function() {
$scope.updateRoute();
};
$scope.onShow = function(promise) {
};
$scope.getRouteOptions = function() {
return {
mode: "tree"
};
};
$scope.setRouteOptions = function(options) {
$scope.updateRoute();
};
$scope.onRefresh = function() {
};
$scope.onSort = function(column) {
if (column) {
column.sort = true;
column.desc = column.desc !== undefined && !column.desc;
column.sortCss = column.desc ? "slick-sort-indicator-desc" : "slick-sort-indicator-asc";
}
$scope.onRefresh();
};
var first = null;
$scope.parse = function(schema) {
var columns = _.map(schema.columns, function(col) {
return new Column($scope, col);
});
var last = null;
var draggable = false;
var loaders = _.map(schema.nodes, function(node) {
var loader = new Loader($scope, node, DataSource);
if (last) {
last.child = loader;
}
if (loader.draggable) {
draggable = true;
}
return last = loader;
});
$scope.viewTitle = schema.title;
$scope.columns = columns;
$scope.loaders = loaders;
$scope.draggable = draggable;
first = _.first(loaders);
first.domain = $scope._domain;
first.context = $scope._context;
// recursive tree (parent -> child on same object)
if (loaders.length === 2 && first.model === last.model) {
last.child = last;
$scope._countOn = _.last(schema.nodes).parent;
}
};
$scope.onNext = function() {
return first && first.onNext();
};
$scope.onPrev = function() {
return first && first.onPrev();
};
$scope.canNext = function() {
return first && first.canNext();
};
$scope.canPrev = function() {
return first && first.canPrev();
};
$scope.pagerText = function() {
return first ? first.pagerText() : "";
};
$scope.resetPager = function() {
if (first) {
first.resetPager();
}
};
$scope.onClick = function(e, options) {
var loader = options.loader,
record = options.record;
var target = $(e.target);
if (target.is('img,i')) {
target = target.parent();
}
if (e.type === 'click' && !target.is('.tree-button')) {
return;
}
var action = target.attr('x-action') || loader.action;
if (!action) {
return;
}
var $handler = ActionService.handler($scope.$new(), $(e.currentTarget), {
action: action
});
var model = loader.model;
var context = record.$record;
$handler.scope.record = context;
$handler.scope.getContext = function() {
return _.extend({
_model: model
}, context);
};
$handler.onClick().then(function(res){
});
};
$scope.$on('on:tab-reload', function(e, tab) {
if ($scope === e.targetScope && $scope.onRefresh) {
$scope.onRefresh();
}
});
}
/**
* Column controller.
*
*/
function Column(scope, col) {
this.css = col.type || 'string';
this.name = col.name;
this.title = col.title || col.autoTitle;
if (this.title === null || this.title === undefined) {
this.title = _.humanize(col.name);
}
if (col.type == 'button') {
this.title = null;
}
this.cellCss = function(record) {
return this.css;
};
this.cellText = function(record) {
if (col.type === 'button') {
var template = "---";
var item = _.findWhere(record.$node.items, { type: 'button', name: col.name });
if (item) {
template = "<a href='javascript:' class='tree-button' x-action='"+ item.onClick +"'>";
if (item.icon) {
if (item.icon.indexOf('fa') === 0) {
template += "<i class='fa " + item.icon + "'></i>";
} else {
template += "<img width='16px' src='"+ item.icon +"'>";
}
}
if (item.title) {
template += item.title;
}
template += "</a>";
}
return template;
}
var value = record[this.name];
if (value === undefined || value === null) {
return '---';
}
var selection = (record.$selection || {})[this.name];
if (selection) {
var cmp = col.type === "integer" ? function(a, b) { return a == b ; } : _.isEqual;
var res = _.find(selection, function(item){
return cmp(item.value, value);
}) || {};
if (col.widget === 'ImageSelect' && res.icon) {
var image = "<img style='max-height: 24px;' src='" + (res.icon || res.value) + "'>";
if (col.labels === false) {
return image;
}
return image + " " + res.title;
}
return res.title;
}
var type = col.type;
if (type === 'reference') {
type = 'many-to-one';
}
var item = _.findWhere(record.$node.items, { type: 'field', as: col.name });
var attrs = _.extend({}, item, col);
var fn = ui.formatters[type];
if (fn) {
value = fn(attrs, value, record);
}
return value === undefined || value === null ? '---' : value;
};
}
/**
* Node loader.
*
*/
function Loader(scope, node, DataSource) {
var ds = DataSource.create(node.model);
var names = _.pluck(node.items, 'name');
var domain = null;
if (node.parent) {
domain = "self." + node.parent + ".id = :parentId";
ds._page.limit = -1;
}
if (node.domain) {
if (domain) {
domain = '(' + domain + ') AND (' + node.domain + ')';
} else {
domain = node.domain;
}
}
this.node = node;
this.child = null;
this.model = node.model;
this.action = node.onClick;
this.draggable = node.draggable;
this.getDomain = function(context) {
var _domain = domain,
_context = context;
if (_domain && this.domain) {
_domain = "(" + this.domain + ") AND (" + domain + ")";
}
_domain = _domain || this.domain;
_context = _.extend({}, this.context, context);
return {
domain: _domain,
context: _context
};
};
this.resetPager = function () {
ds._page.from = 0;
};
this.load = function(item, callback) {
var context = _.extend({}, scope._context),
current = item && item.$record;
var sortOn = _.filter(scope.columns, function (col) { return col.sort; });
var sortBy = _.map(sortOn, function (col) {
var field = _.findWhere(node.items, { as: col.name });
if (field) {
return col.desc ? '-' + field.name : field.name;
}
});
sortBy = _.compact(sortBy).join(',') || node.orderBy;
if (scope.getContext) {
context = _.extend(context, scope.getContext());
}
if (current) {
context.parentId = current.id;
}
if (scope._countOn) {
context._countOn = scope._countOn;
} else if (this.child) {
var child = this.child.node;
context._childOn = {
model: child.model,
parent: child.parent
};
}
var opts = _.extend(this.getDomain(context), {
fields: names,
action: scope._viewAction
});
if (sortBy) {
opts.sortBy = sortBy.split(',');
}
var promise = ds.search(opts);
promise.success(function(records) {
if (callback) {
callback(accept(item, records));
}
});
return promise;
};
this.move = function(item, callback) {
var record = item.$record,
parent = { id: item.$parentId };
record[node.parent || scope._countOn] = parent;
return ds.save(record).success(function(rec) {
record.version = rec.version;
if (callback) {
callback(rec);
}
});
};
var that = this;
function accept(current, records) {
var fields = node.items,
parent = current && current.$record,
child = that.child;
return _.map(records, function(record) {
var $id = _.uniqueId('row');
var $parent = current ? current.$id : null;
var item = {
'$id': $id,
'$model': node.model,
'$node': node,
'$record': record,
'$selection': {},
'$parent': $parent,
'$parentId': parent && parent.id,
'$parentModel': current && current.$model,
'$draggable': node.draggable,
'$folder': child && (record._children === undefined || record._children > 0)
};
item.$expand = function(callback) {
if (child) {
return child.load(this, callback);
}
};
item.$move = function(callback) {
return that.move(this, callback);
};
item.$click = function(e) {
if (node.onClick) {
scope.onClick(e, {
loader: that,
record: item,
parent: parent
});
}
};
_.each(fields, function(field) {
var name = field.as || field.name;
item[name] = record[field.name];
item.$selection[name] = field.selectionList;
});
return item;
});
}
var page = {};
ds.on('change', function(e, _records, _page) {
page = _page;
});
this.canNext = function() {
return ds.canNext();
};
this.canPrev = function() {
return ds.canPrev();
};
this.onNext = function() {
ds.next(names).success(function(records){
scope.setRootNodes(accept(null, records));
});
};
this.onPrev = function() {
ds.prev(names).success(function(records){
scope.setRootNodes(accept(null, records));
});
};
this.pagerText = function() {
if (page && page.from !== undefined) {
if (page.total === 0) return null;
return _t("{0} to {1} of {2}", page.from + 1, page.to, page.total);
}
};
}
ui.directive('uiViewTree', function(){
return {
replace: true,
link: function(scope, element, attrs) {
var table = element.find('.tree-table > table');
table.treetable({
indent: 16,
expandable: true,
clickableNodeNames: true,
nodeIdAttr: "id",
parentIdAttr: "parent",
branchAttr: "folder",
onNodeCollapse: function onNodeCollapse() {
var node = this,
row = node.row;
if (node._state === "collapsed") {
return;
}
node._state = "collapsed";
table.treetable("collapseNode", row.data("id"));
adjustCols();
},
onNodeExpand: function onNodeExpand() {
var node = this,
row = this.row,
record = row.data('$record');
if (node._loading || node._state === "expanded") {
return;
}
node._state = "expanded";
if (node._loaded) {
table.treetable("expandNode", row.data("id"));
return adjustCols();
}
node._loading = true;
if (record.$expand) {
record.$expand(function(records) {
acceptNodes(records, node);
node._loading = false;
node._loaded = true;
adjustCols();
});
}
}
});
function acceptNodes(records, after) {
var rows = _.map(records, makeRow);
table.treetable("loadBranch", after, rows);
}
function makeRow(record) {
var tr = $('<tr>')
.attr('data-id', record.$id)
.attr('data-parent', record.$parent)
.attr('data-folder', record.$folder);
tr.data('$record', record);
_.each(scope.columns, function(col) {
$('<td>').html(col.cellText(record)).appendTo(tr);
});
if (scope.draggable && (record.$folder || scope._countOn || !record.$parent)) {
makeDroppable(tr);
}
if (record.$draggable || (scope.draggable && scope._countOn)) {
makeDraggable(tr);
}
tr.on('click dblclick taphold', function(e) {
record.$click(e);
});
return tr[0];
}
function onDrop(e, ui) {
/* jshint validthis: true */
var row = ui.draggable,
record = row.data('$record'),
current = $(this).data('$record'),
node = table.treetable("node", row.data("id")),
nodeParent = node.parentNode();
table.treetable("move", node.id, $(this).data("id"));
// make sure to remove expander icon if no children left
if (nodeParent && nodeParent.children.length === 0) {
nodeParent.row.removeClass('expanded');
nodeParent.row.removeClass('branch');
nodeParent.row.addClass('leaf');
nodeParent.treeCell.off('click.treetable');
nodeParent.treeCell.off('keydown.treetable');
nodeParent.indenter.empty();
}
record.$parentId = current.$record.id;
record.$move(function(result) {
});
}
function isParent(source, target) {
var parent = target.parent().find('[data-id=' + target.data('parent') + ']');
if (parent.data('id') === source.data('id')) {
return true;
}
if (parent.length) {
return isParent(source, parent);
}
return false;
}
function makeDroppable(row) {
row.droppable({
accept: function(draggable, x) {
var source = draggable.data('$record'),
target = row.data('$record');
// don't allow moving parent to child
if (scope._countOn) {
return !isParent(draggable, $(this));
}
return source && target && target.$model === source.$parentModel;
},
hoverClass: "accept",
drop: onDrop,
over: function(e, ui) {
var row = ui.draggable;
if(this != row[0] && !$(this).is(".expanded")) {
table.treetable("expandNode", $(this).data("id"));
}
}
});
}
function makeDraggable(row) {
var record = row.data('$record');
if (!record.$draggable && !scope._countOn) {
return;
}
row.draggable({
helper: function() {
return $('<span></span>').append(row.children('td:first').clone());
},
opacity: 0.75,
containment: 'document',
refreshPositions: true,
revert: "invalid",
revertDuration: 300,
delay: 300,
scroll: true
});
}
function clear() {
var tree = table.data('treetable');
if (tree === undefined) {
return;
}
_.each(tree.roots, function(node) {
tree.unloadBranch(node);
node.row.remove();
delete tree.tree[node.id];
});
tree.nodes.length = 0;
tree.roots.length = 0;
}
scope.onRefresh = function() {
var root = _.first(scope.loaders);
if (root) {
root.load(null, function(nodes) {
scope.setRootNodes(nodes);
});
}
};
scope.setRootNodes = function(nodes) {
clear();
acceptNodes(nodes);
};
scope.onHeaderClick = function (event, column) {
if (!event.shiftKey) {
_.each(scope.columns, function (col) {
if (col !== column) {
col.sort = false;
col.desc = undefined;
col.sortCss = null;
}
});
}
scope.onSort(column);
};
var watcher = scope.$watch('loaders', function treeLoadersWatch(loaders) {
if (loaders === undefined) {
return;
}
watcher();
var root = _.first(loaders);
if (root) {
root.load(null, acceptNodes).then(adjustCols);
}
});
var adjustCounter = 0;
function adjustCols() {
if (element.is(':hidden')) {
if (adjustCounter++ < 10) {
_.delay(adjustCols, 100);
}
return;
}
adjustCounter = 0;
var tds = table.find('tr:first').find('td');
var ths = element.find('.tree-header').find('th');
var widths = [];
if (tds.length !== ths.length) {
return;
}
tds.each(function() {
widths.push($(this).outerWidth());
});
ths.each(function(i) {
$(this).width(widths[i] - 12);
});
}
scope.$onAdjust(adjustCols, 100);
table.on('mousedown.treeview', 'tbody tr', function(e) {
table.find('tr.selected').removeClass('selected');
$(this).addClass("selected");
});
},
template:
'<div class="tree-view-container" ui-attach-scroll="> .tree-table">'+
'<table class="tree-header">'+
'<thead>'+
'<tr>'+
'<th ng-repeat="column in columns" ng-class="column.css" ng-click="onHeaderClick($event, column)">' +
'<span>{{column.title}}</span>'+
'<span ng-if="column.sort" class="slick-sort-indicator" ng-class="column.sortCss"></span>'+
'</th>'+
'</tr>'+
'</thead>'+
'</table>'+
'<div class="tree-table">'+
'<table>'+
'<tbody></tbody>'+
'</table>'+
'</div>'+
'</div>'
};
});
TreePortletCtrl.$inject = ['$scope', '$element', 'DataSource', 'ActionService'];
function TreePortletCtrl($scope, $element, DataSource, ActionService) {
TreeViewCtrl.call(this, $scope, $element, DataSource, ActionService);
$scope.showPager = true;
$scope.$on("on:new", function(e) {
$scope.resetPager();
$scope.onRefresh();
});
$scope.$on("on:edit", function(e) {
$scope.resetPager();
$scope.onRefresh();
});
}
ui.directive('uiPortletTree', function(){
return {
controller: TreePortletCtrl,
template: '<div ui-view-tree></div>'
};
});
})();

View File

@ -0,0 +1,137 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.directive('uiDialog', function() {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
var onBeforeClose = scope.$eval(attrs.onBeforeClose);
var onOpen = scope.$eval(attrs.onOpen);
var onClose = scope.$eval(attrs.onClose);
var onOK = scope.$eval(attrs.onOk);
var cssClass = attrs.css;
var buttons = scope.$eval(attrs.buttons) || [];
if(_.isEmpty(buttons) || (_.isUndefined(onClose) || _.isFunction(onClose))) {
buttons.push({
text: _t('Close'),
'class': 'btn button-close',
click: function() {
element.dialog('close');
}
});
}
if(_.isEmpty(buttons) || _.isUndefined(onOK) || _.isFunction(onOK)){
buttons.push({
text: _t('OK'),
'class': 'btn btn-primary button-ok',
click: function() {
if (onOK) {
onOK();
}
else
element.dialog('close');
}
});
}
var dialog = element.dialog({
dialogClass: 'ui-dialog-responsive ' + (cssClass || ''),
resizable: false,
draggable: false,
autoOpen: false,
closeOnEscape: true,
modal: true,
zIndex: 1100,
show: {
effect: 'fade',
duration: 300
},
buttons: buttons
});
// fix IE11 issue
if (axelor.browser.msie && axelor.browser.rv) {
var headerHeight = 46;
var footerHeight = 52;
var dialogMargin = 64;
function onResize() {
var availableHeight = $(window).height() - headerHeight - footerHeight - dialogMargin - 8;
var contentHeight = element.children().height();
var myHeight = Math.min(availableHeight, contentHeight);
element.height(myHeight);
}
dialog.on('dialogopen', function (e, ui) {
$(window).on('resize', onResize);
setTimeout(onResize);
});
dialog.on('dialogclose', function (e, ui) {
$(window).off('resize', onResize);
});
scope.$on('$destroy', function() {
$(window).off('resize', onResize);
});
element.addClass('ui-dialog-ie11');
}
// focus the previous visible dialog
dialog.on('dialogclose', function(e, ui){
var target = element.data('$target');
if (target) {
return setTimeout(function(){
if (!axelor.device.mobile) {
var input = target.find(':input:first');
input.addClass('x-focus').focus().select();
setTimeout(function () {
input.removeClass('x-focus');
});
}
});
}
$('body .ui-dialog:visible:last').focus();
});
dialog.on('dialogopen', onOpen)
.on('dialogclose', onClose)
.on('dialogbeforeclose', onBeforeClose);
scope.$on('$destroy', function(){
if (dialog) {
if (dialog.data('dialog')) {
dialog.dialog('destroy');
}
dialog.remove();
dialog = null;
}
});
}
};
});
})();

View File

@ -0,0 +1,285 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
MenuBarCtrl.$inject = ['$scope', '$element'];
function MenuBarCtrl($scope, $element) {
this.isDivider = function(item) {
return !item.title && !item.icon;
};
this.isSubMenu = function(item) {
return item && item.items && item.items.length > 0;
};
$scope.isImage = function (menu) {
return menu.icon && menu.icon.indexOf('fa-') !== 0;
};
$scope.isIcon = function (menu) {
return menu.icon && menu.icon.indexOf('fa-') === 0;
};
$scope.canShowTitle = function(menu) {
return menu.showTitle === null || menu.showTitle === undefined || menu.showTitle;
};
}
ui.directive('uiMenuBar', function() {
return {
replace: true,
controller: MenuBarCtrl,
scope: {
menus: '=',
handler: '='
},
link: function(scope, element, attrs, ctrl) {
ctrl.handler = scope.handler;
scope.onMenuClick = _.once(function onMenuClick(e) {
element.find('.dropdown-toggle').dropdown();
$(e.currentTarget).dropdown('toggle');
});
},
template:
"<ul class='nav menu-bar'>" +
"<li class='menu dropdown button-menu' ng-class='::{\"button-menu\": menu.isButton, \"has-icon\": menu.icon}' ng-repeat='menu in ::menus'>" +
"<a href='' class='dropdown-toggle btn' ng-class='::{\"btn\": menu.isButton}' data-toggle='dropdown' ng-click='onMenuClick($event)'>" +
"<img ng-if='::isImage(menu)' ng-src='{{menu.icon}}'> " +
"<i class='fa {{::menu.icon}}' ng-if='::isIcon(menu)'></i> " +
"<span class='menu-title' ng-show='::canShowTitle(menu)'>{{::menu.title}}</span> " +
"<b class='caret'></b>" +
"</a>" +
"<ul ui-menu='menu'></ul>" +
"</li>" +
"</ul>"
};
});
ui.directive('uiMenu', function() {
return {
replace: true,
require: '^uiMenuBar',
scope: {
menu: '=uiMenu'
},
link: function(scope, element, attrs, ctrl) {
},
template:
"<ul class='dropdown-menu'>" +
"<li ng-repeat='item in ::menu.items' ui-menu-item='item'>" +
"</ul>"
};
});
ui.directive('uiMenuItem', ['$compile', 'ActionService', function($compile, ActionService) {
return {
replace: true,
require: '^uiMenuBar',
scope: {
item: '=uiMenuItem'
},
link: function(scope, element, attrs, ctrl) {
var item = scope.item;
var handler = null;
scope.field = item;
scope.isDivider = ctrl.isDivider(item);
scope.isSubMenu = ctrl.isSubMenu(item);
if (item.action) {
handler = ActionService.handler(ctrl.handler, element, {
action: item.action,
prompt: item.prompt
});
element.addClass("action-item").attr("x-name", item.name);
}
scope.isRequired = function(){};
scope.isValid = function(){};
attrs = {
hidden: !!item.hidden,
readonly: !!item.readonly
};
scope.attr = function(name, value) {
attrs[name] = value;
};
scope.isReadonly = function(){
if (attrs.readonly) return true;
if (_.isFunction(item.active)) {
return !item.active();
}
return false;
};
scope.isHidden = function(){
if (attrs.hidden) return true;
if (_.isFunction(item.visible)) {
return !item.visible();
}
return false;
};
var form = element.parents('.form-view:first');
var formScope = form.data('$scope');
if (formScope) {
formScope.$watch('record', function menubarRecordWatch(rec) {
scope.record = rec;
});
}
scope.onClick = function(e) {
element.parents('.dropdown').dropdown('toggle');
if (scope.isSubMenu) return;
if (item.action) {
return handler.onClick();
}
if (_.isFunction(item.click)) {
return item.click(e);
}
};
scope.cssClass = function() {
if (scope.isDivider) {
return 'divider';
}
if (scope.isSubMenu) {
return 'dropdown-submenu';
}
};
if (scope.isSubMenu) {
$compile('<ul ui-menu="item"></ul>')(scope, function(cloned, scope) {
element.append(cloned);
});
}
},
template:
"<li ng-class='cssClass()' ui-widget-states ng-show='!isHidden()'>" +
"<a href='' ng-show='isReadonly()' class='disabled'>{{item.title}}</a>" +
"<a href='' ng-show='!isDivider && !isReadonly()' ng-click='onClick($event)'>{{item.title}}</a>" +
"</li>"
};
}]);
ui.directive('uiToolbarAdjust', function() {
return function (scope, element, attrs) {
var elemMenubar = null;
var elemToolbar = null;
var elemSiblings = null;
var elemToolbarMobile = null;
function setup() {
elemMenubar = element.children('.view-menubar');
elemToolbar = element.children('.view-toolbar');
elemSiblings = element.children(':not(.view-menubar,.view-toolbar,.view-toolbar-mobile)');
elemToolbarMobile = element.children('.view-toolbar-mobile').hide();
var running = false;
scope.$onAdjust(function () {
if (running) {
return;
}
running = true;
try {
adjust();
} finally {
running = false;
}
});
scope.$callWhen(adjust, function () {
return element.is(':visible');
});
}
function hideAndShow(first, second, visibility) {
[elemMenubar, elemToolbar, elemToolbarMobile].forEach(function (elem) {
elem.hide().css('visibility', 'hidden');
});
[first, second].forEach(function (elem) {
if (elem && element.is(':visible')) {
elem.show().css('visibility', visibility || '');
}
});
}
function adjust() {
if (elemMenubar === null) {
return;
}
var width = element.width() - 8;
elemSiblings.each(function (i) {
width -= $(this).width();
});
if (axelor.device.small) {
if (width > elemToolbarMobile.width() + elemMenubar.width()) {
hideAndShow(elemToolbarMobile, elemMenubar);
} else if (width > elemToolbarMobile.width()) {
hideAndShow(elemToolbarMobile);
} else if (width > elemMenubar.width()) {
hideAndShow(elemMenubar);
}
return;
}
function canShow(first, second) {
hideAndShow(first, second, 'hidden');
if (width > first.width() + second.width()) {
first.css('visibility', '');
second.css('visibility', '');
return true;
}
return false;
}
canShow(elemToolbar, elemMenubar) ||
canShow(elemToolbarMobile, elemMenubar) ||
canShow(elemToolbar, $()) ||
canShow(elemToolbarMobile, $());
}
scope.waitForActions(setup, 100);
};
});
})();

View File

@ -0,0 +1,447 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
NavMenuCtrl.$inject = ['$scope', '$element', 'MenuService', 'NavService'];
function NavMenuCtrl($scope, $element, MenuService, NavService) {
$scope.menus = []; // the first four visible menus
$scope.more = []; // rest of the menus
var hasSideBar = axelor.config['view.menubar.location'] !== 'top';
MenuService.all().then(function(response) {
var res = response.data,
data = res.data;
var items = {};
var all = [];
_.each(data, function(item) {
items[item.name] = item;
if (item.children === undefined) {
item.children = [];
}
});
_.each(data, function(item) {
if (hasSideBar && !item.top) {
return;
}
if (!item.parent) {
return all.push(item);
}
var parent = items[item.parent];
if (parent) {
parent.children.push(item);
}
});
$scope.menus = all;
$scope.more = all;
$scope.extra = {
title: 'More',
children: $scope.more
};
});
this.isSubMenu = function(item) {
return item && item.children && item.children.length > 0;
};
this.onItemClick = function(item) {
if (item.action && !this.isSubMenu(item)) {
NavService.openTabByName(item.action);
}
};
$scope.hasImage = function (menu) {
return menu.icon && menu.icon.indexOf('fa-') !== 0 && menu.icon.indexOf('empty') != -1;
};
$scope.hasIcon = function (menu) {
return menu.icon && menu.icon.indexOf('fa-') === 0 && menu.icon.indexOf('empty') != -1;
};
$scope.hasText = function (menu) {
return !menu.icon || menu.icon.indexOf('empty') === -1;
};
}
ui.directive('navMenuBar', function() {
return {
replace: true,
controller: NavMenuCtrl,
scope: true,
link: function(scope, element, attrs, ctrl) {
var elemTop,
elemSub,
elemMore;
var siblingsWidth = 0;
var adjusting = false;
element.hide();
function adjust() {
if (adjusting) {
return;
}
adjusting = true;
var count = 0;
var parentWidth = element.parent().width() - 32;
elemMore.hide();
elemTop.hide();
elemSub.hide();
while (count < elemTop.length) {
var elem = $(elemTop[count]).show();
var width = siblingsWidth + element.width();
if (width > parentWidth) {
elem.hide();
// show more...
elemMore.show();
width = siblingsWidth + element.width();
if (width > parentWidth) {
count--;
$(elemTop[count]).hide();
}
break;
}
count++;
}
if (count === elemTop.length) {
elemMore.hide();
}
while(count < elemTop.length) {
$(elemSub[count++]).show();
}
adjusting = false;
}
function setup() {
element.siblings().each(function () {
siblingsWidth += $(this).width();
});
elemTop = element.find('.nav-menu.dropdown:not(.nav-menu-more)');
elemMore = element.find('.nav-menu.dropdown.nav-menu-more');
elemSub = elemMore.find('.dropdown-menu:first > .dropdown-submenu');
element.show();
adjust();
$(window).on("resize.menubar", adjust);
}
element.on('$destroy', function () {
if (element) {
$(window).off("resize.menubar");
element = null;
}
});
var unwatch = scope.$watch('menus', function navMenusWatch(menus, old) {
if (!menus || menus.length === 0 || menus === old) {
return;
}
unwatch();
setTimeout(setup, 100);
});
},
template:
"<ul class='nav nav-menu-bar'>" +
"<li class='nav-menu dropdown' ng-class='{empty: !hasText(menu)}' ng-repeat='menu in menus track by menu.name'>" +
"<a href='javascript:' class='dropdown-toggle' data-toggle='dropdown'>" +
"<img ng-if='hasImage(menu)' ng-src='{{menu.icon}}'> " +
"<i ng-if='hasIcon(menu)' class='fa {{menu.icon}}'></i> " +
"<span ng-if='hasText(menu)' ng-bind='menu.title'></span> " +
"<b class='caret'></b>" +
"</a>" +
"<ul nav-menu='menu'></ul>" +
"</li>" +
"<li class='nav-menu nav-menu-more dropdown' style='display: none;'>" +
"<a href='javascript:' class='dropdown-toggle' data-toggle='dropdown'>" +
"<span x-translate>More</span>" +
"<b class='caret'></b>" +
"</a>" +
"<ul nav-menu='extra'></ul>" +
"</li>" +
"</ul>"
};
});
ui.directive('navMenu', function() {
return {
replace: true,
require: '^navMenuBar',
scope: {
menu: '=navMenu'
},
link: function(scope, element, attrs, ctrl) {
},
template:
"<ul class='dropdown-menu'>" +
"<li ng-repeat='item in menu.children track by item.name' nav-menu-item='item'>" +
"</ul>"
};
});
ui.directive('navMenuItem', ['$compile', function($compile) {
return {
replace: true,
require: '^navMenuBar',
scope: {
item: '=navMenuItem'
},
link: function(scope, element, attrs, ctrl) {
var item = scope.item;
scope.isSubMenu = ctrl.isSubMenu(item);
scope.isActionMenu = !!item.action;
scope.onClick = function (e, item) {
ctrl.onItemClick(item);
};
if (ctrl.isSubMenu(item)) {
element.addClass("dropdown-submenu");
$compile('<ul nav-menu="item"></ul>')(scope, function(cloned, scope) {
element.append(cloned);
});
}
},
template:
"<li>" +
"<a href='javascript:' ng-click='onClick($event, item)'>{{item.title}}</a>" +
"</li>"
};
}]);
ui.directive('navMenuFav', function() {
return {
replace: true,
controller: ['$scope', '$location', 'DataSource', 'NavService', function ($scope, $location, DataSource, NavService) {
var ds = DataSource.create("com.axelor.meta.db.MetaMenu", {
domain: "self.user = :__user__ and self.link is not null"
});
$scope.items = [];
function update() {
ds.search({
fields: ["id", "name", "title", "link"],
sortBy: ["-priority"]
}).success(function (records, page) {
$scope.items = records;
});
}
function add(values, callback) {
var item = _.findWhere($scope.items, { link: values.link });
if (item && item.title === values.title) {
return callback();
}
if (item) {
item.title = values.title;
} else {
item = values;
item.name = values.link;
item.user = {
id: axelor.config['user.id']
};
item.hidden = true;
}
ds.save(item).success(update).then(callback, callback);
}
$scope.addFav = function () {
var link = $location.path();
if (link === "/") {
return;
}
var tab = NavService.getSelected() || {};
var vs = tab.$viewScope || {};
var title = tab.title || (vs.schema || {}).title || "";
if (vs.record && vs.record.id > 0) {
title = title + " (" + vs.record.id + ")";
}
var item = _.findWhere($scope.items, { link: link });
if (item) {
title = item.title;
}
var dialog = axelor.dialogs.box("<input type='text' style='width: 100%;box-sizing: border-box;height: 28px;margin: 0;'>", {
title: _t('Add to favorites...'),
buttons: [{
text: _t('Cancel'),
'class': 'btn btn-default',
click: function (e) {
$(this).dialog('close');
}
}, {
text: _t('OK'),
'class': 'btn btn-primary',
click: function (e) {
title = dialog.find("input").val();
add({ title: title, link: link }, function () {
dialog.dialog('close');
});
}
}]
});
setTimeout(function () {
dialog.find("input").val(title).focus().select();
});
};
$scope.manageFav = function () {
NavService.openTabByName('menus.fav');
};
function onUpdate(e, _ds) {
if (ds !== _ds && ds._model === _ds._model) {
update();
}
}
$scope.$on("ds:saved", onUpdate);
$scope.$on("ds:removed", onUpdate);
update();
}],
template:
"<ul class='dropdown-menu'>" +
"<li><a href='' ng-click='addFav()' x-translate>Add to favorites...</a></li>" +
"<li class='divider'></li>" +
"<li ng-repeat='item in items track by item.name'><a ng-href='#{{item.link}}'>{{item.title}}</a></li>" +
"<li class='divider'></li>" +
"<li><a href='' ng-click='manageFav()' x-translate>Organize favorites...</a></li>" +
"</ul>"
};
});
ui.directive('navMenuTasks', function() {
return {
replace: true,
controller: ['$scope', '$location', 'TagService', 'NavService', function ($scope, $location, TagService, NavService) {
var TEAM_TASK = "com.axelor.team.db.TeamTask";
function taskText(count) {
var n = count || 0;
if (n <= 0) return _t('no tasks');
return n > 1 ? _t('{0} tasks', n) : _t('{0} task', n);
}
function update(data) {
var counts = data || {};
if (counts.current) {
counts.css = 'badge-primary';
}
if (counts.pending) {
counts.css = 'badge-important';
}
counts.currentText = taskText(counts.current);
counts.pendingText = taskText(counts.pending);
counts.total = Math.min(99, counts.current);
$scope.counts = counts;
}
TagService.listen(function (data) {
update(data.tasks || {});
});
$scope.showTasks = function (type) {
NavService.openTabByName('team.tasks.' + type);
};
function onDataChange(e, ds) {
if (ds._model === TEAM_TASK) {
TagService.find();
}
}
$scope.$on('ds:saved', onDataChange);
$scope.$on('ds:removed', onDataChange);
update({});
}],
template:
"<li class='dropdown'>" +
"<a href='' class='nav-link-tasks dropdown-toggle' data-toggle='dropdown'>" +
"<i class='fa fa-bell'></i>" +
"<span class='badge' ng-show='counts.css' ng-class='counts.css'>{{counts.total}}</span>" +
"</a>" +
"<ul class='dropdown-menu'>" +
"<li>" +
"<a href='' ng-click='showTasks(\"due\")'>" +
"<span class='nav-link-user-name' x-translate>Tasks due</span>" +
"<span class='nav-link-user-sub' ng-class='{\"fg-red\": counts.pending > 0}'>{{counts.pendingText}}</span>" +
"</a>" +
"</li>" +
"<li class='divider'></li>" +
"<li>" +
"<a href='' ng-click='showTasks((\"todo\"))'>" +
"<span class='nav-link-user-name' x-translate>Tasks todo</span>" +
"<span class='nav-link-user-sub'>{{counts.currentText}}</span>" +
"</a>" +
"</li>" +
"</ul>" +
"</li>"
};
});
})();

View File

@ -0,0 +1,85 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.directive('uiNavTabs', function() {
return {
restrict: 'EA',
replace: true,
link: function(scope, elem, attrs) {
if (scope.singleTabOnly) {
return elem.parent().addClass("view-tabs-single");
}
scope.$watch('tabs.length', function navTabsWatch(value, oldValue){
if (value != oldValue) elem.trigger('adjust:tabs');
});
var menu = $();
setTimeout(function () {
elem.bsTabs();
elem.on('contextmenu', '.nav-tabs-main > li > a', showMenu);
menu = elem.find('#nav-tabs-menu');
menu.css({
position: 'absolute',
zIndex: 1000
}).hide();
});
function showMenu(e) {
var tabElem = $(e.target).parents('li:first');
var tabScope = tabElem.data('$scope');
if (!tabScope || !tabScope.tab) {
return;
}
e.preventDefault();
e.stopPropagation();
scope.current = tabScope.tab;
scope.$timeout(function () {
var offset = elem.offset();
menu.show().css({
left: e.pageX - offset.left,
top: e.pageY - offset.top
});
$(document).on('click.nav-tabs-menu', hideMenu);
});
}
function hideMenu(e) {
scope.$timeout(function () {
scope.current = null;
menu.hide();
});
$(document).off('click.nav-tabs-menu');
}
},
templateUrl: 'partials/nav-tabs.html'
};
});
})();

View File

@ -0,0 +1,436 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
ui.directive('uiNavTree', ['MenuService', 'TagService', function(MenuService, TagService) {
return {
scope: {
itemClick: "&"
},
controller: ["$scope", "$q", function ($scope, $q) {
var items = [];
var menus = [];
var nodes = {};
var searchItems = [];
var deferred = $q.defer();
var handler = $scope.itemClick();
function canAccept(item) {
return item.left || item.left === undefined;
}
this.onClick = function (e, menu) {
if (menu.action && (menu.children||[]).length === 0) {
handler(e, menu);
}
};
this.load = function (data) {
if (!data || !data.length) return;
items = data;
items.forEach(function (item) {
nodes[item.name] = item;
item.children = [];
});
items.forEach(function (item) {
var node = nodes[item.parent];
if (node) {
node.children.push(item);
} else if (canAccept(item)){
menus.push(item);
item.icon = item.icon || 'fa-bars';
item.iconBackground = item.iconBackground || 'green';
}
});
var markForSidebar = function (item) {
item.sidebar = true;
item.children.forEach(markForSidebar);
};
menus.forEach(markForSidebar);
items.forEach(function (item) {
if (item.children.length === 0) {
delete item.children;
var label = item.title;
var parent = nodes[item.parent];
var lastParent;
while (parent) {
lastParent = parent;
parent = nodes[parent.parent];
if (parent) {
label = lastParent.title + "/" + label;
}
}
searchItems.push(_.extend({
title: item.title,
label: label,
action: item.action,
category: lastParent ? lastParent.name : '',
categoryTitle: lastParent ? lastParent.title : ''
}));
}
});
$scope.menus = menus;
$scope.searchItems = searchItems;
deferred.resolve();
};
this.update = function (data) {
if (!data || data.length === 0) return;
data.forEach(function (item) {
var node = nodes[item.name];
if (node) {
node.tag = item.tag;
node.tagStyle = item.tagStyle;
if (node.tagStyle) {
node.tagCss = "label-" + node.tagStyle;
}
}
});
};
var that = this;
TagService.listen(function (data) {
that.update(data.tags);
});
function findProp(node, name) {
if (node[name]) {
return node[name];
}
var parent = nodes[node.parent];
if (parent) {
return findProp(parent, name);
}
return null;
}
function updateTabStyle(tab) {
if (tab.icon || tab.fa) {
return;
}
var node = _.findWhere(nodes, { action: tab.action, sidebar: true });
if (node) {
tab.icon = tab.icon || findProp(node, 'icon');
tab.color = tab.color || findProp(node, 'iconBackground');
if (tab.icon && tab.icon.indexOf('fa') === 0) {
tab.fa = tab.icon;
delete tab.icon;
} else {
tab.fa = tab.fa || findProp(node, 'fa');
}
if (tab.icon) {
tab.fa = null;
}
if (tab.color && tab.color.indexOf('#') != 0) {
tab.topCss = 'bg-' + tab.color;
tab.fa = tab.fa ? tab.fa + ' fg-' + tab.color : null;
tab.color = null;
}
}
}
MenuService.updateTabStyle = function (tab) {
deferred.promise.then(function () {
updateTabStyle(tab);
});
};
}],
link: function (scope, element, attrs, ctrl) {
var input = element.find('input');
scope.showSearch = !!axelor.device.mobile;
scope.toggleSearch = function (show) {
input.val('');
if (!axelor.device.mobile) {
scope.showSearch = show === undefined ? !scope.showSearch : show;
}
};
scope.onShowSearch = function () {
scope.showSearch = true;
setTimeout(function () {
input.val('').focus();
});
};
input.attr('placeholder', _t('Search...'));
input.blur(function (e) {
scope.$timeout(function () {
scope.toggleSearch(false);
});
});
input.keydown(function (e) {
if (e.keyCode === 27) { // escape
scope.$timeout(function () {
scope.toggleSearch(false);
});
}
});
function search(request, response) {
var term = request.term;
var items = _.filter(scope.searchItems, function (item) {
var text = item.categoryTitle + '/' + item.label;
var search = term;
if (search[0] === '/') {
search = search.substring(1);
text = item.title;
}
text = text.replace('/', '').toLowerCase();
if (search[0] === '"' || search[0] === '=') {
search = search.substring(1);
if (search.indexOf('"') === search.length - 1) {
search = search.substring(0, search.length - 1);
}
return text.indexOf(search) > -1;
}
var parts = search.toLowerCase().split(/\s+/);
for (var i = 0; i < parts.length; i++) {
if (text.indexOf(parts[i]) === -1) {
return false;
}
}
return parts.length > 0;
});
response(items);
}
MenuService.all().success(function (res) {
ctrl.load(res.data);
input.autocomplete({
source: search,
select: function (e, ui) {
ctrl.onClick(e, ui.item);
scope.$timeout(function () {
scope.toggleSearch(false);
});
},
appendTo: element.parent(),
open: function () {
element.children('.nav-tree').hide();
},
close: function (e) {
element.children('.nav-tree').show();
}
});
input.data('autocomplete')._renderMenu = function (ul, items) {
var all = _.groupBy(items, 'category');
var that = this;
scope.menus.forEach(function (menu) {
var found = all[menu.name];
if (found) {
ul.append($("<li class='ui-menu-category'>").html(menu.title));
found.forEach(function (item) {
that._renderItemData(ul, item);
});
}
});
};
});
},
replace: true,
template:
"<div>" +
"<div class='nav-search-toggle' ng-show='!showSearch'>" +
"<i ng-click='onShowSearch()' class='fa fa-angle-down'></i>" +
"</div>" +
"<div class='nav-search' ng-show='showSearch'>" +
"<input type='text'>" +
"</div>" +
"<ul class='nav nav-tree'>" +
"<li ng-repeat='menu in menus track by menu.name' ui-nav-sub-tree x-menu='menu'></li>" +
"</ul>" +
"</div>"
};
}]);
ui.directive('uiNavSubTree', ['$compile', function ($compile) {
return {
scope: {
menu: "="
},
require: "^uiNavTree",
link: function (scope, element, attrs, ctrl) {
var menu = scope.menu;
if (menu.icon && menu.icon.indexOf('fa') === 0) {
menu.fa = menu.icon;
delete menu.icon;
}
if (menu.tagStyle) {
menu.tagCss = "label-" + menu.tagStyle;
}
if (menu.children) {
var sub = $(
"<ul class='nav ui-nav-sub-tree'>" +
"<li ng-repeat='child in menu.children track by child.name' ui-nav-sub-tree x-menu='child'></li>" +
"</ul>");
sub = $compile(sub)(scope);
sub.appendTo(element);
}
setTimeout(function () {
var icon = element.find("span.nav-icon:first");
if (menu.iconBackground && icon.length > 0) {
var cssName = menu.parent ? 'color' : 'background-color';
var clsName = menu.parent ? 'fg-' : 'bg-';
if (!menu.parent) {
icon.addClass("fg-white");
}
if (menu.iconBackground.indexOf("#") === 0) {
icon.css(cssName, menu.iconBackground);
} else {
icon.addClass(clsName + menu.iconBackground);
}
// get computed color value
var color = icon.css(cssName);
var bright = d3.rgb(color).brighter(.3).toString();
// add hover effect
element.hover(function () {
icon.css(cssName, bright);
}, function () {
icon.css(cssName, color);
});
// use same color for vertical line
if (!menu.parent) {
element.css("border-left-color", color);
element.hover(function () {
element.css("border-left-color", color);
}, function () {
element.css("border-left-color", bright);
});
}
}
});
var animation = false;
function show(el) {
var parent = el.parent("li");
if (animation || parent.hasClass('open')) {
return;
}
function done() {
parent.addClass('open');
parent.removeClass('animate');
el.height('');
animation = false;
}
hide(parent.siblings("li.open").children('ul'));
animation = true;
parent.addClass('animate');
el.height(el[0].scrollHeight);
setTimeout(done, 300);
}
function hide(el) {
var parent = el.parent("li");
if (animation || !parent.hasClass('open')) {
return;
}
function done() {
parent.removeClass('open');
parent.removeClass('animate');
animation = false;
}
animation = true;
el.height(el.height())[0].offsetHeight;
parent.addClass('animate');
el.height(0);
setTimeout(done, 300);
}
element.on('click', '> a', function (e) {
e.preventDefault();
if (animation) return;
var $list = element.children('ul');
element.parents('.nav-tree').find('li.active').not(element).removeClass('active');
element.addClass('active');
if (menu.action && (menu.children||[]).length === 0) {
scope.$applyAsync(function () {
ctrl.onClick(e, menu);
});
}
if ($list.length === 0) return;
if (element.hasClass('open')) {
hide($list);
} else {
show($list);
}
});
if (menu.help) {
setTimeout(function () {
var tooltip = element.children('a')
.addClass('has-help')
.tooltip({
html: true,
title: menu.help,
placement: 'right',
delay: { show: 500, hide: 100 },
container: 'body'
});
});
}
},
replace: true,
template:
"<li ng-class='{folder: menu.children, tagged: menu.tag }' data-name='{{::menu.name}}'>" +
"<a href='#'>" +
"<img class='nav-image' ng-if='::menu.icon' ng-src='{{::menu.icon}}'></img>" +
"<span class='nav-icon' ng-if='::menu.fa'><i class='fa' ng-class='::menu.fa'></i></span>" +
"<span ng-show='menu.tag' ng-class='menu.tagCss' class='nav-tag label'>{{menu.tag}}</span>" +
"<span class='nav-title'>{{::menu.title}}</span>" +
"</a>" +
"</li>"
};
}]);
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,473 @@
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
'use strict';
var ui = angular.module('axelor.ui');
ui.directive('uiDeleteButton', [function () {
return {
link: function (scope, element, attrs) {
},
replace: true,
template:
"<div class='btn-group delete-button'>" +
"<button class='btn' ng-click='onDelete()' ng-if='hasButton(\"delete\")' ng-disabled='!canDelete()' title='{{ \"Delete\" | t}}'>" +
"<i class='fa fa-trash-o'></i> <span ng-if='::!tbTitleHide' x-translate>Delete</span>" +
"</button>" +
"<button class='btn dropdown-toggle' data-toggle='dropdown' ng-if='hasButton(\"archive\")' ng-disabled='!canArchive()'>" +
"<i class='fa fa-caret-down'></i>" +
"</button>" +
"<ul class='dropdown-menu' ng-if='hasButton(\"archive\")'>" +
"<li><a href='' ng-click='onArchive()' x-translate>Archive</a></li>" +
"<li><a href='' ng-click='onUnarchive()' x-translate>Unarchive</a></li>" +
"</ul>" +
"</div>"
};
}]);
ui.directive('uiUpdateButton', ['$compile', function ($compile) {
return {
scope: {
handler: '='
},
link: function (scope, element, attrs) {
var menu = element.find('.update-menu'),
toggleButton = null;
scope.visible = false;
scope.onMassUpdate = function (e) {
if (menu && menu.is(':visible')) {
hideMenu();
return;
}
toggleButton = $(e.currentTarget);
toggleButton.addClass("active");
scope.onShow(e, menu);
$(document).on('mousedown.update-menu', onMouseDown);
scope.$applyAsync(function () {
scope.visible = true;
});
};
scope.onCancel = function () {
hideMenu();
};
scope.canMassUpdate = function () {
return true;
};
if (scope.handler && scope.handler.canMassUpdate) {
scope.canMassUpdate = scope.handler.canMassUpdate;
}
function hideMenu() {
$(document).off('mousedown.update-menu', onMouseDown);
if (toggleButton) {
toggleButton.removeClass("active");
}
scope.$applyAsync(function () {
scope.visible = false;
});
return menu.hide();
}
function onMouseDown(e) {
var all = $(menu).add(toggleButton);
if (all.is(e.target) || all.has(e.target).length > 0) {
return;
}
all = $('.ui-widget-overlay,.ui-datepicker:visible,.ui-dialog:visible,.ui-menu:visible');
if (all.is(e.target) || all.has(e.target).length > 0) {
return;
}
if(menu){
hideMenu();
}
}
// append box after the button
scope.$timeout(function () {
element.parents('.view-container').after(menu);
});
scope.$on('$destroy', function() {
$(document).off('mousedown.update-menu', onMouseDown);
if (menu) {
menu.remove();
menu = null;
}
});
},
replace: true,
template:
"<button class='btn update-menu-button' ng-click='onMassUpdate($event)' ng-disabled='!canMassUpdate()' >" +
"<i class='fa fa-caret-down'></i>" +
"<div ui-update-menu x-handler='handler' x-visible='visible'></div>" +
"</button>"
};
}]);
ui.directive('uiUpdateDummy', function () {
return {
require: '^uiUpdateForm',
scope: {
record: '='
},
controller: ['$scope', '$element', 'DataSource', 'ViewService', function($scope, $element, DataSource, ViewService) {
var parent = $scope.$parent;
var handler = parent.handler;
$scope._viewParams = {
model: handler._model,
views: []
};
ui.ViewCtrl($scope, DataSource, ViewService);
ui.FormViewCtrl.call(this, $scope, $element);
function prepare(fields) {
var schema = {
cols: 1,
type: 'form',
items: _.values(fields)
};
$scope.fields = fields;
$scope.schema = schema;
$scope.schema.loaded = true;
}
var initialized = false;
$scope.show = function () {
if (initialized) return;
initialized = true;
var unwatch = parent.$watch('fields', function massFieldsWatch(fields) {
if (_.isEmpty(fields)) return;
unwatch();
prepare(fields);
});
};
$scope.setEditable();
$scope.show();
}],
link: function (scope, element, attrs) {
element.hide();
},
template: "<div class='hide' ui-view-form x-handler='true'></div>"
};
});
ui.directive('uiUpdateForm', function () {
function findFields(fields, items) {
var all = {};
var accept = function (field) {
var name = field.name;
if (!field.massUpdate) return;
if (/^(id|version|selected|archived|((updated|created)(On|By)))$/.test(name)) return;
if (field.large || field.unique) return;
switch (field.type) {
case 'one-to-many':
case 'many-to-many':
case 'binary':
return;
}
if (field.target) {
field.canNew = false;
field.canEdit = false;
}
field.hidden = false;
field.required = false;
field.readonly = false;
field.onChange = null;
field.placeholder = field.placeholder || field.title;
all[name] = field;
};
_.each(fields, function (field, name) { accept(field); });
_.each(items, function (item) {
var field = fields[item.name];
if (field) {
accept(_.extend({}, field, item, { type: field.type }));
}
});
return all;
}
return {
replace: true,
controller: ['$scope', 'ViewService', function ($scope, ViewService) {
$scope.filters = [{}];
$scope.options = [];
$scope.onInit = _.once(function (view) {
var handler = $scope.handler;
var promise = ViewService.getFields(handler._model);
promise.success(function (fields) {
$scope.fields = findFields(fields, view.items);
$scope.options = _.sortBy(_.values($scope.fields), 'title');
$scope.record = {};
});
});
$scope.addFilter = function (filter) {
var all = $scope.filters;
var last = _.last(all);
if (last && !last.field) return;
if (all.length > 0 && all.length === $scope.options.length) return;
$scope.filters.push(filter || {});
$scope.updateSelection();
};
$scope.removeFilter = function(filter) {
var index = $scope.filters.indexOf(filter);
if (index > -1) {
$scope.filters.splice(index, 1);
}
if ($scope.filters.length === 0) {
$scope.addFilter();
}
};
$scope.notSelected = function (filter) {
return function (opt) {
return filter.field === opt.name || !opt.selected;
};
};
var values = null;
var canUpdate = false;
function updateValues(record) {
var keys = _.pluck($scope.filters, 'field'),
vals = {};
_.each(keys, function (key) {
if (key) {
vals[key] = (record || {})[key];
if (vals[key] === undefined) {
vals[key] = null;
}
}
});
values = vals;
canUpdate = !_.isEmpty(values);
}
$scope.updateSelection = function updateSelection () {
var selected = _.pluck($scope.filters, 'field');
_.each($scope.options, function (opt) {
opt.selected = selected.indexOf(opt.name) > -1;
});
updateValues($scope.record);
};
$scope.$watch('record', updateValues, true);
$scope.canUpdate = function () {
return canUpdate;
};
$scope.updateAll = false;
$scope.applyUpdate = function () {
var handler = $scope.handler;
var ds = handler._dataSource;
function doUpdate() {
var promise, items;
items = _.map(handler.selection, function(index) {
return handler.dataView.getItem(index);
});
items = _.pluck(items, "id");
if ($scope.updateAll) {
items = null;
} else if (items.length === 0) {
return $scope.onCancel();
}
promise = ds.updateMass(values, items);
promise.success(function () {
handler.onRefresh();
$scope.onCancel();
});
}
var count;
if ($scope.updateAll) {
count = ds._page.total;
} else if(handler.selection && handler.selection.length > 0) {
count = handler.selection.length;
} else {
return;
}
var message = _t('Do you really want to update all {0} record(s)?', count);
axelor.dialogs.confirm(message, function (confirmed) {
if (confirmed) {
doUpdate();
}
});
};
}],
link: function (scope, element, attrs) {
scope.onSelect = function (name) {
scope.updateSelection();
setTimeout(adjustEditors);
};
scope.clearFilter = function() {
scope.filters.length = 0;
scope.addFilter();
scope.record = {};
adjustEditors();
};
scope.remove = function(filter) {
scope.removeFilter(filter);
adjustEditors();
};
scope.onCancel = function () {
if (scope.$parent.onCancel) {
scope.$parent.onCancel();
}
};
function adjustEditors() {
element.find('[x-place-for] [x-field]').each(function () {
var editor = $(this);
var parent = editor.data('$parent');
editor.appendTo(parent);
});
_.each(scope.filters, function (filter) {
adjustEditor(filter.field);
});
}
function adjustEditor(name) {
var span = element.find('[x-place-for=' + name + ']');
var editor = element.find('[x-field=' + name + '].form-item-container,[x-field=' + name + '].boolean-item').first();
var parent = editor.data('$parent');
if (!parent) {
parent = editor.parent();
editor.data('$parent', parent);
}
editor.appendTo(span);
}
},
template:
"<form class='form-inline update-form filter-form'>" +
"<strong x-translate>Mass Update</strong> " +
"<hr>" +
"<table class='form-layout'>" +
"<tr ng-repeat='filter in filters' class='form-inline'>" +
"<td class='filter-remove'>" +
"<a href='' ng-click='remove(filter)'><i class='fa fa-times'></i></a>" +
"</td>" +
"<td class='form-item'>" +
"<span class='form-item-container'>" +
"<select ng-model='filter.field' ng-options='v.name as v.title for v in options | filter:notSelected(filter)' ng-change='onSelect(filter.field)'></select>" +
"</span>" +
"</td>" +
"<td class='form-item' x-place-for='{{filter.field}}'>" +
"</td>" +
"</tr>" +
"</table>" +
"<div class='links'>"+
"<a href='' ng-click='addFilter()' x-translate>Add Field</a>" +
"<span class='divider'>|</span>"+
"<a href='' ng-click='clearFilter()' x-translate>Clear</a>" +
"</div>" +
"<div ui-update-dummy x-record='record'></div>"+
"</form>"
};
});
ui.directive('uiUpdateMenu', function () {
return {
replace: true,
scope: {
handler: '='
},
link: function (scope, element, attrs) {
scope.$parent.onShow = function (event, menu) {
scope.handler._viewPromise.then(function (view) {
var elem = $(event.currentTarget);
if (scope.onInit) {
scope.onInit(view);
}
menu.show();
menu.position({
my: "left top",
at: "left bottom",
of: elem
});
});
};
},
template:
"<div class='update-menu filter-menu' ui-watch-if='$parent.visible'>" +
"<div ui-update-form></div>" +
"<hr>" +
"<div class='form-inline'>" +
"<button class='btn btn-small' ng-disabled='!canUpdate()' ng-click='applyUpdate()'><span x-translate>Update</span></button> " +
"<button class='btn btn-small' ng-click='onCancel()'><span x-translate>Cancel</span></button> " +
"<label class='checkbox update-all'>" +
"<input type='checkbox' ng-model='updateAll'> <span x-translate>Update all</span>" +
"</label> " +
"</div>" +
"</div>"
};
});
})();