mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
1/ WEB UI Cost Schedule features to :
- delete cost items - duplicate cost items - delete individual cost values - update cost values on the fly - Shitf + LMB to recursively hide or show nested rows 2/ Improved UI for cost schedules 3/ Reuse css styles across pages with import statements 4/ Remove console/print statements
This commit is contained in:
@@ -41,49 +41,3 @@ th {
|
||||
/* zoom */
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
#cost-items tr:hover {
|
||||
background-color: #f0f0f0;
|
||||
color : black;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
background-color: #363636;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.cost-values-form {
|
||||
position: absolute; /* Set initial position to absolute */
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 9999;
|
||||
cursor: move; /* Change cursor to move */
|
||||
}
|
||||
|
||||
.cost-values-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.cost-values-table th, .cost-values-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.cost-values-table th {
|
||||
background-color: #f2f2f2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
@import url("./shared.css");
|
||||
@import url("./components/card.css");
|
||||
@import url("./components/contextMenu.css");
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
/* prevent text from being selceted */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
td:not(:first-child) {
|
||||
text-align: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
td {
|
||||
|
||||
border-right: 1px solid #ddd;
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
|
||||
}
|
||||
|
||||
.nested {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expand-collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.caret-cell {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-radius: 6px;
|
||||
background-color:inherit;
|
||||
padding: 6px 8px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color:inherit;
|
||||
transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
|
||||
height: inherit;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: #0366d6;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.3);
|
||||
}
|
||||
|
||||
|
||||
#cost-items tr:hover, .highlighted {
|
||||
background-color: #ffb936;
|
||||
color : black;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #ffb936;
|
||||
color : black;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
background-color: #363636;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.cost-values-form {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 9999;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.cost-values-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.cost-values-table th, .cost-values-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.cost-values-table th {
|
||||
background-color: #444444;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background-color: #28a745; /* GitHub green color */
|
||||
border: none;
|
||||
color: #fff; /* White text color */
|
||||
padding: 0.5em 1em; /* Adjust padding as needed */
|
||||
cursor: pointer;
|
||||
border-radius: 6px; /* Slightly rounded corners */
|
||||
font-weight: bold; /* Make the text bold */
|
||||
text-decoration: none; /* Remove underline */
|
||||
display: inline-block; /* Make the button inline */
|
||||
transition: background-color 0.3s ease; /* Add a smooth transition */
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background-color: #218838; /* Darker green color on hover */
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
@import url("./components/card.css");
|
||||
@import url("./components/contextMenu.css");
|
||||
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
|
||||
@@ -1,205 +1,4 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 1.5rem;
|
||||
--nav-height: 0.625rem;
|
||||
--border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--text-color: #e0e0e0;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--border-color: #222;
|
||||
}
|
||||
|
||||
*:not(.tabulator-cell) {
|
||||
border-radius: var(--border-radius) !important;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--blender-window-background, var(--bg-color));
|
||||
color: var(--blender-text, var(--text-color));
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
position: relative;
|
||||
padding: var(--margin-small);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: var(--margin-large);
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--blender-selected-object, var(--nav-link-hover-color));
|
||||
}
|
||||
|
||||
.warning,
|
||||
.warning-icon {
|
||||
color: var(--blender-info-warning, var(--warning-color));
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--blender-panel-header, var(--bg-color));
|
||||
font-size: var(--base-font-size);
|
||||
color: var(--blender-text, var(--text-color))
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
@import url("./shared.css");
|
||||
|
||||
.table-container {
|
||||
display: block;
|
||||
@@ -212,18 +11,6 @@ footer p {
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
background-color: var(--blender-button-background, var(--bg-color));
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
/* ------------ Overwriting Tabulator CSS rules ------------ */
|
||||
|
||||
:root.light .tabulator-header,
|
||||
@@ -278,15 +65,12 @@ button:hover {
|
||||
color: var(--blender-text);
|
||||
}
|
||||
|
||||
|
||||
|
||||
:root.blender .tabulator-selected .tabulator-cell,
|
||||
:root.blender .tabulator-row.tabulator-selected {
|
||||
background-color: var(--blender-active-highlight) !important;
|
||||
color: var(--blender-active-text-highlight) !important;
|
||||
}
|
||||
|
||||
|
||||
:root.blender .tabulator-selected:hover .tabulator-cell,
|
||||
:root.blender .tabulator-row.tabulator-selected:hover {
|
||||
filter: brightness(1.2)
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
--margin-tiny: 0.125rem;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-tiny: 0.125rem;
|
||||
--padding-small: 0.625rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 1.5rem;
|
||||
--nav-height: 0.625rem;
|
||||
--border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--text-color: #e0e0e0;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--border-color: #464444;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
--border-color: #222;
|
||||
}
|
||||
|
||||
*:not(.tabulator-cell) {
|
||||
border-radius: var(--border-radius) !important;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--blender-window-background, var(--bg-color));
|
||||
color: var(--blender-text, var(--text-color));
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#container {
|
||||
position: relative;
|
||||
padding: var(--margin-small);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
height: var(--nav-height);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: var(--margin-large);
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--blender-selected-object, var(--nav-link-hover-color));
|
||||
}
|
||||
|
||||
.warning,
|
||||
.warning-icon {
|
||||
color: var(--blender-info-warning, var(--warning-color));
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#client-list {
|
||||
position: absolute;
|
||||
top: calc(0 + var(--nav-height));
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#client-list.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#connected-list-div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.client {
|
||||
padding: var(--margin-small);
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-details {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
padding-left: var(--padding-small);
|
||||
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out;
|
||||
background-color: var(--blender-panel-background, var(--nav-bg-color));
|
||||
margin-top: var(--margin-tiny);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.client-details.show {
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.client-detail {
|
||||
border-bottom: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
padding: var(--padding-small);
|
||||
}
|
||||
|
||||
#show-connected-button {
|
||||
width: auto;
|
||||
background-color: var(--blender-panel-header, var(--bg-color));
|
||||
font-size: var(--base-font-size);
|
||||
color: var(--blender-text, var(--text-color))
|
||||
}
|
||||
|
||||
#show-connected-button:hover,
|
||||
.scroll-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll-button {
|
||||
font-size: var(--base-font-size);
|
||||
margin-left: var(--margin-tiny);
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
|
||||
text-align: right;
|
||||
padding: var(--padding-tiny);
|
||||
border-top: 1px solid var(--blender-tab-outline, var(--nav-border-color));
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
color: var(--blender-text, var(--nav-link-color));
|
||||
margin-right: var(--margin-small);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
background-color: var(--blender-button-background, var(--bg-color));
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
@@ -35,6 +35,7 @@ function handleBlenderConnect(blenderId) {
|
||||
$("#blender-count").text(function (i, text) {
|
||||
return parseInt(text, 10) + 1;
|
||||
});
|
||||
console.log("Blender connected", blenderId);
|
||||
}
|
||||
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
@@ -53,15 +54,25 @@ function removeTableElement(blenderId) {
|
||||
}
|
||||
|
||||
|
||||
const costValueCallbacks = {
|
||||
'editCostValues': editCostValues,
|
||||
'addCostValue': addCostValue,
|
||||
'deleteCostValue': deleteCostValue,
|
||||
};
|
||||
|
||||
function handleCostValueData(data) {
|
||||
|
||||
const costItemId = data.data["cost_value"]["cost_item_id"];
|
||||
const costValueId = data.data["cost_value"]["cost_value_id"];
|
||||
console.log("Handling cost value data", costItemId, costValueId);
|
||||
CostUI.addNewCostValueRow(costItemId, costValueId);
|
||||
CostUI.addNewCostValueRow(costItemId, costValueId, costValueCallbacks);
|
||||
|
||||
}
|
||||
|
||||
function deleteCostValue(costItemId, costValueId) {
|
||||
executeOperator({ type: "deleteCostValue", costItemId: costItemId, costValueId: costValueId });
|
||||
}
|
||||
|
||||
function handleConnectedClients(data) {
|
||||
$("#blender-count").text(data.length);
|
||||
|
||||
@@ -77,8 +88,12 @@ function handleCostValuesData(data) {
|
||||
callbacks: {
|
||||
'editCostValues': editCostValues,
|
||||
'addCostValue': addCostValue,
|
||||
'deleteCostValue': deleteCostValue,
|
||||
},
|
||||
});
|
||||
|
||||
CostUI.highlightCostItem(data.data["cost_values"]["cost_item_id"]);
|
||||
|
||||
}
|
||||
|
||||
function addCostValue(costItemId) {
|
||||
@@ -93,6 +108,10 @@ function editCostValues(costItemId, costValues) {
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCostItem(costItemId) {
|
||||
executeOperator({ type: "deleteCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
@@ -165,14 +184,23 @@ function handleCostSchedulesData(data) {
|
||||
const card = CostUI.createCard(costSchedule.Name, mainContainer, callback);
|
||||
costScheduleDiv.append(card);
|
||||
});
|
||||
|
||||
const firstCostSchedule = costSchedules[0];
|
||||
if (firstCostSchedule) {
|
||||
loadCostSchedule(firstCostSchedule.id, blenderId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function handleCostItemsData(data) {
|
||||
console.log("Handling cost items data", data);
|
||||
CostUI.createCostSchedule({
|
||||
data: data.data["cost_items"],
|
||||
blenderID: data.blenderId,
|
||||
callbacks: {
|
||||
"addCostItem": addCostItem,
|
||||
"deleteCostItem": deleteCostItem,
|
||||
"duplicateCostItem": duplicateCostItem,
|
||||
"selectAssignedElements": selectAssignedElements,
|
||||
'editCostItemName': editCostItemName,
|
||||
'enableEditingCostValues': enableEditingCostValues,
|
||||
@@ -180,6 +208,10 @@ function handleCostItemsData(data) {
|
||||
});
|
||||
}
|
||||
|
||||
function duplicateCostItem(costItemId) {
|
||||
executeOperator({ type: "duplicateCostItem", costItemId: costItemId });
|
||||
}
|
||||
|
||||
function executeOperator(operator, blenderId) {
|
||||
const msg = {
|
||||
sourcePage: "cost",
|
||||
@@ -201,4 +233,4 @@ function getCostSchedules(blenderId) {
|
||||
|
||||
function enableEditingCostValues(costItemId) {
|
||||
executeOperator({ type: "enableEditingCostValues", costItemId: costItemId});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ function connectSocket() {
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("connect", handleWebConnect);
|
||||
socket.on("connected_clients", handleConnectedClients);
|
||||
socket.on("default_data", handleDefaultData);
|
||||
//socket.on("default_data", handleDefaultData);
|
||||
socket.on("theme_data", handleThemeData);
|
||||
socket.on("drawings_data", handleDrawingsData);
|
||||
socket.on("svg_data", handleSvgData);
|
||||
@@ -37,6 +37,7 @@ function connectSocket() {
|
||||
|
||||
// function used to get drawings data from Bonsai
|
||||
function handleWebConnect() {
|
||||
console.log("Handle Web Connect")
|
||||
getDrawingsData();
|
||||
}
|
||||
|
||||
@@ -73,7 +74,6 @@ function handleDefaultData(data) {
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender disconnected: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeSelectOption(blenderId);
|
||||
@@ -97,8 +97,6 @@ function handleConnectedClients(data) {
|
||||
}
|
||||
|
||||
function handleThemeData(themeData) {
|
||||
// console.log(themeData);
|
||||
|
||||
function arrayToRgbString(arr) {
|
||||
const [r, g, b, a] = arr.map((num) => Math.round(num * 255));
|
||||
if (a !== undefined) {
|
||||
@@ -129,6 +127,7 @@ function handleThemeData(themeData) {
|
||||
}
|
||||
|
||||
function handleDrawingsData(data) {
|
||||
console.log("Running handleDrawingsData");
|
||||
const blenderId = data["blenderId"];
|
||||
|
||||
console.log(data);
|
||||
|
||||
@@ -23,11 +23,10 @@ export class CostUI {
|
||||
for (let i = 0; i < columnHeaders.length; i++) {
|
||||
const th = document.createElement("th");
|
||||
th.textContent = columnHeaders[i];
|
||||
th.style.position = "relative"; // Required for the resizer handle
|
||||
th.style.position = "relative";
|
||||
tr.appendChild(th);
|
||||
|
||||
// Add resizer handle
|
||||
if (i < columnHeaders.length - 1) { // No resizer for the last column
|
||||
if (i < columnHeaders.length - 1) {
|
||||
const resizer = document.createElement("div");
|
||||
resizer.classList.add("resizer");
|
||||
th.appendChild(resizer);
|
||||
@@ -39,12 +38,8 @@ export class CostUI {
|
||||
table.appendChild(tbody);
|
||||
document.getElementById("cost-items").appendChild(table);
|
||||
|
||||
// Add CSS to set column widths, resizer styles, hover effect, and color scheme
|
||||
CostUI.addTableStyles(id);
|
||||
|
||||
// Create context menu
|
||||
CostUI.createContextMenu(callbacks);
|
||||
|
||||
table.get_blender_id = function() {
|
||||
return this.getAttribute("id").split("-")[2];
|
||||
};
|
||||
@@ -69,19 +64,17 @@ export class CostUI {
|
||||
}
|
||||
|
||||
static createContextMenu(callbacks) {
|
||||
// Create context menu
|
||||
const contextMenu = document.createElement("div");
|
||||
contextMenu.id = "context-menu";
|
||||
contextMenu.classList.add("context-menu");
|
||||
contextMenu.innerHTML = `
|
||||
<button id="edit-cost-values-button">Edit Cost Values</button>
|
||||
<button id="add-cost-item-button">Add sub-cost</button>
|
||||
<button id="edit-cost-values-button">Edit</button>
|
||||
<button id="delete-button">Delete</button>
|
||||
<button id="duplicate-button">Duplicate</button>
|
||||
<button id="delete-button">Delete</button>
|
||||
`;
|
||||
document.body.appendChild(contextMenu);
|
||||
|
||||
// Add event listeners for context menu
|
||||
document.addEventListener("contextmenu", function(event) {
|
||||
event.preventDefault();
|
||||
const targetRow = event.target.closest("tr");
|
||||
@@ -92,7 +85,6 @@ export class CostUI {
|
||||
contextMenu.style.left = `${event.pageX}px`;
|
||||
contextMenu.style.top = `${event.pageY}px`;
|
||||
|
||||
// Store the target row in the context menu for later use
|
||||
contextMenu.targetRow = targetRow;
|
||||
contextMenu.targetCell = targetCell;
|
||||
} else {
|
||||
@@ -109,7 +101,6 @@ export class CostUI {
|
||||
|
||||
const editCostValuesButton = document.getElementById("edit-cost-values-button");
|
||||
editCostValuesButton.addEventListener("click", function() {
|
||||
console.log("Edit cost values executed");
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
const targetCell = document.getElementById("context-menu").targetCell;
|
||||
if (targetRow) {
|
||||
@@ -133,24 +124,45 @@ export class CostUI {
|
||||
addButton.dataset.listenerAdded = "true";
|
||||
}
|
||||
|
||||
document.getElementById("delete-button").addEventListener("click", function() {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
// Implement your delete action here
|
||||
console.log("Delete row:", targetRow.getAttribute("id"));
|
||||
targetRow.remove();
|
||||
}
|
||||
});
|
||||
const deleteButton = document.getElementById("delete-button");
|
||||
if (deleteButton && !deleteButton.hasListener) {
|
||||
deleteButton.addEventListener("click", function() {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
let expandedState = JSON.parse(localStorage.getItem('expandedState')) || {};
|
||||
expandedState = CostUI.deleteRow(targetRow, expandedState)
|
||||
localStorage.setItem('expandedState', JSON.stringify(expandedState));
|
||||
callbacks.deleteCostItem(costItemId);
|
||||
}
|
||||
});
|
||||
deleteButton.hasListener = true;
|
||||
}
|
||||
|
||||
document.getElementById("duplicate-button").addEventListener("click", function() {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
// Implement your duplicate action here
|
||||
console.log("Duplicate row:", targetRow.getAttribute("id"));
|
||||
const newRow = targetRow.cloneNode(true);
|
||||
targetRow.parentElement.appendChild(newRow);
|
||||
}
|
||||
const duplicateButton = document.getElementById("duplicate-button");
|
||||
if (duplicateButton && !duplicateButton.hasListener) {
|
||||
duplicateButton.addEventListener("click", function() {
|
||||
const targetRow = document.getElementById("context-menu").targetRow;
|
||||
if (targetRow) {
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
callbacks.duplicateCostItem(costItemId);
|
||||
}
|
||||
});
|
||||
duplicateButton.hasListener = true;
|
||||
}
|
||||
}
|
||||
|
||||
static deleteRow(targetRow, expandedState) {
|
||||
if (!targetRow) {
|
||||
return
|
||||
}
|
||||
delete expandedState[targetRow.id]
|
||||
targetRow.remove()
|
||||
const subRows = document.querySelectorAll(`[parent-id='${targetRow.id}']`);
|
||||
subRows.forEach(subRow => {
|
||||
CostUI.deleteRow(subRow, expandedState)
|
||||
});
|
||||
return expandedState
|
||||
}
|
||||
|
||||
static addResizer(resizer) {
|
||||
@@ -176,18 +188,15 @@ export class CostUI {
|
||||
}
|
||||
|
||||
static generateColorScheme(baseColor) {
|
||||
// This function generates a color scheme based on the base color
|
||||
// For simplicity, we'll just lighten the base color for each level
|
||||
const levels = 7; // Number of levels of nesting
|
||||
const levels = 10;
|
||||
const colorScheme = [];
|
||||
for (let i = 0; i < levels; i++) {
|
||||
colorScheme.push(CostUI.lightenColor(baseColor, i * 7));
|
||||
colorScheme.push(CostUI.lightenColor(baseColor, i * 5));
|
||||
}
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
static lightenColor(color, percent) {
|
||||
// This function lightens a color by a given percentage
|
||||
const num = parseInt(color.slice(1), 16),
|
||||
amt = Math.round(2.55 * percent),
|
||||
R = (num >> 16) + amt,
|
||||
@@ -199,7 +208,7 @@ export class CostUI {
|
||||
static applyColorScheme(tableId, colorScheme) {
|
||||
const rows = document.querySelectorAll(`#${tableId} tbody tr`);
|
||||
rows.forEach((row, index) => {
|
||||
const level = index % colorScheme.length; // Assuming level is determined by index for simplicity
|
||||
const level = index % colorScheme.length;
|
||||
row.style.backgroundColor = colorScheme[level];
|
||||
});
|
||||
}
|
||||
@@ -209,7 +218,7 @@ export class CostUI {
|
||||
const colorPicker = document.createElement("input");
|
||||
colorPicker.type = "color";
|
||||
colorPicker.id = "color-picker";
|
||||
colorPicker.value = "#ff0000"; // Default color
|
||||
colorPicker.value = "#ff0000";
|
||||
const colorText = document.createElement("p");
|
||||
colorText.textContent = "Select a color to change row color:";
|
||||
document.getElementById("UI").appendChild(colorText);
|
||||
@@ -231,25 +240,26 @@ export class CostUI {
|
||||
}
|
||||
|
||||
static createCostItem(data, container, nestingLevel = 0, parentID = null, callbacks = {}) {
|
||||
data.forEach(obj => {
|
||||
const row = CostUI.createRow(obj, nestingLevel, parentID, callbacks);
|
||||
data.forEach(costItem => {
|
||||
const row = CostUI.createRow(costItem, nestingLevel, parentID, callbacks);
|
||||
container.appendChild(row);
|
||||
|
||||
if (obj.is_nested_by && obj.is_nested_by.length > 0) {
|
||||
CostUI.createCostItem(obj.is_nested_by, container, nestingLevel + 1, obj.id, callbacks);
|
||||
if (costItem.is_nested_by && costItem.is_nested_by.length > 0) {
|
||||
CostUI.createCostItem(costItem.is_nested_by, container, nestingLevel + 1, costItem.id, callbacks);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static createRow(obj, nestingLevel, parentID, callbacks = {}) {
|
||||
const row = CostUI.createTableRow(obj, nestingLevel, parentID);
|
||||
const expandButton = CostUI.createExpandButton(obj);
|
||||
const nameCell = CostUI.createNameCell(obj, nestingLevel, expandButton, callbacks);
|
||||
const totalCostQuantityCell = CostUI.createTableCell(obj.TotalCostQuantity);
|
||||
const unitSymbolCell = CostUI.createTableCell(obj.UnitSymbol);
|
||||
const totalAppliedValueCell = CostUI.createTableCell(obj.TotalAppliedValue);
|
||||
const totalCostCell = CostUI.createTotalCostCell(obj);
|
||||
const flexContainerCell = CostUI.createFlexContainerCell(obj, callbacks);
|
||||
static createRow(costItem, nestingLevel, parentID, callbacks = {}) {
|
||||
const totalQuantity = costItem.TotalCostQuantity ? parseFloat(costItem.TotalCostQuantity).toFixed(2) : '-';
|
||||
const row = CostUI.createTableRow(costItem, nestingLevel, parentID);
|
||||
const expandButton = CostUI.createExpandButton(costItem);
|
||||
const nameCell = CostUI.createNameCell(costItem, nestingLevel, expandButton, callbacks);
|
||||
const totalCostQuantityCell = CostUI.createTableCell(totalQuantity);
|
||||
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
||||
const totalAppliedValueCell = CostUI.createTableCell(costItem.TotalAppliedValue);
|
||||
const totalCostCell = CostUI.createTotalCostCell(costItem);
|
||||
const flexContainerCell = CostUI.createFlexContainerCell(costItem, callbacks);
|
||||
|
||||
row.appendChild(nameCell);
|
||||
row.appendChild(totalCostQuantityCell);
|
||||
@@ -270,49 +280,167 @@ export class CostUI {
|
||||
return row;
|
||||
}
|
||||
|
||||
static createTableRow(obj, nestingLevel, parentID) {
|
||||
static createTableRow(costItem, nestingLevel, parentID) {
|
||||
const row = document.createElement("tr");
|
||||
row.setAttribute("id", obj.id);
|
||||
row.setAttribute("id", costItem.id);
|
||||
row.setAttribute("parent-id", parentID);
|
||||
if (nestingLevel > 0) {
|
||||
row.classList.add("nested");
|
||||
row.classList.add(`level-${nestingLevel}`);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
static createExpandButton(row) {
|
||||
const expandButton = document.createElement("i");
|
||||
expandButton.classList.add("action-button");
|
||||
expandButton.classList.add("fa-solid");
|
||||
expandButton.classList.add("fa-angle-right");
|
||||
|
||||
static createExpandButton(obj) {
|
||||
const expandButton = document.createElement("button");
|
||||
expandButton.classList.add("toggle-button");
|
||||
if (obj.is_nested_by && obj.is_nested_by.length > 0) {
|
||||
expandButton.textContent = ">";
|
||||
if (row.is_nested_by && row.is_nested_by.length > 0) {
|
||||
expandButton.classList.add('fa-angle-right');
|
||||
} else {
|
||||
expandButton.style.visibility = "hidden";
|
||||
}
|
||||
expandButton.addEventListener("click", function() {
|
||||
CostUI.contractExpandRow.call(this, obj.id);
|
||||
|
||||
expandButton.addEventListener("click", function(event) {
|
||||
event.stopPropagation();
|
||||
let isExpanded = CostUI.isRowExpanded(row.id);
|
||||
console.log("Row is expanded", isExpanded);
|
||||
if (event.shiftKey) {
|
||||
if (isExpanded) {
|
||||
CostUI.contractRow(row.id);
|
||||
CostUI.updateExpandedState(row.id, false, true);
|
||||
} else {
|
||||
CostUI.expandRow(row.id);
|
||||
CostUI.updateExpandedState(row.id, true, true);
|
||||
}
|
||||
} else {
|
||||
if (isExpanded) {
|
||||
CostUI.contractRow(row.id);
|
||||
CostUI.updateExpandedState(row.id, false);
|
||||
}
|
||||
else {
|
||||
CostUI.expandRow(row.id);
|
||||
CostUI.updateExpandedState(row.id, true);
|
||||
}
|
||||
}
|
||||
CostUI.applyExpandedState();
|
||||
});
|
||||
|
||||
return expandButton;
|
||||
}
|
||||
|
||||
static createNameCell(obj, nestingLevel, expandButton, callbacks) {
|
||||
|
||||
static hideRow(id) {
|
||||
const row = document.getElementById(id);
|
||||
if (row) {
|
||||
row.classList.add("nested");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static contractRow(parentId, recursive=false) {
|
||||
CostUI.updateToggleButton(parentId, 'contract');
|
||||
const rows = document.querySelectorAll(`[parent-id='${parentId}']`);
|
||||
rows.forEach(row => {
|
||||
const childId = row.getAttribute('id');
|
||||
CostUI.hideRow(childId);
|
||||
if (recursive || (childId && CostUI.isRowExpanded(childId))) {
|
||||
CostUI.contractRow(childId, recursive)
|
||||
}
|
||||
// CostUI.contractRow(childId);
|
||||
});
|
||||
}
|
||||
|
||||
static showRow(id) {
|
||||
const row = document.getElementById(id);
|
||||
if (row) {
|
||||
row.classList.remove("nested");
|
||||
}
|
||||
}
|
||||
|
||||
static updateToggleButton(id, type='contract') {
|
||||
const toggleButton = document.querySelector(`[id='${id}'] .action-button`);
|
||||
if (!toggleButton) {
|
||||
return;
|
||||
}
|
||||
if (type === 'expand') {
|
||||
toggleButton.classList.remove("fa-angle-right");
|
||||
toggleButton.classList.add("fa-angle-up");
|
||||
}
|
||||
else {
|
||||
toggleButton.classList.remove("fa-angle-up");
|
||||
toggleButton.classList.add("fa-angle-right");
|
||||
}
|
||||
}
|
||||
|
||||
static expandRow(parentId, recursive=false) {
|
||||
CostUI.updateExpandedState(parentId, true);
|
||||
CostUI.updateToggleButton(parentId, 'expand');
|
||||
const rows = document.querySelectorAll(`[parent-id='${parentId}']`);
|
||||
rows.forEach(row => {
|
||||
const childId = row.getAttribute('id');
|
||||
CostUI.showRow(childId);
|
||||
if (recursive || (childId && CostUI.isRowExpanded(childId))) {
|
||||
CostUI.expandRow(childId, recursive);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static updateExpandedState(id, isExpanded, recursive=false) {
|
||||
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || {};
|
||||
expandedState[id] = isExpanded;
|
||||
localStorage.setItem('expandedState', JSON.stringify(expandedState));
|
||||
if (recursive) {
|
||||
const rows = document.querySelectorAll(`[parent-id='${id}']`);
|
||||
rows.forEach(row => {
|
||||
CostUI.updateExpandedState(row.id, isExpanded, recursive);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static applyExpandedState() {
|
||||
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || CostUI.getAllCostItemsIds().reduce((acc, id) => {
|
||||
acc[id] = true;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
console.log("Expanded state", expandedState);
|
||||
|
||||
Object.keys(expandedState).forEach(id => {
|
||||
if (expandedState[id]) {
|
||||
CostUI.expandRow(id);
|
||||
} else {
|
||||
CostUI.contractRow(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static isRowExpanded(id) {
|
||||
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || {};
|
||||
return expandedState[id] || false;
|
||||
}
|
||||
|
||||
static createNameCell(costItem, nestingLevel, expandButton, callbacks) {
|
||||
const nameCell = document.createElement("td");
|
||||
const nameInput = document.createElement("input");
|
||||
nameInput.value = obj.name ? obj.name : "Unnamed";
|
||||
nameInput.value = costItem.name ? costItem.name : "Unnamed";
|
||||
|
||||
nameInput.addEventListener("change", function() {
|
||||
callbacks.editCostItemName ? callbacks.editCostItemName(obj.id, this.value) : null;
|
||||
callbacks.editCostItemName ? callbacks.editCostItemName(costItem.id, this.value) : null;
|
||||
});
|
||||
|
||||
nameInput.addEventListener("keydown", function(event) {
|
||||
if (event.key === "Enter") {
|
||||
callbacks.editCostItemName ? callbacks.editCostItemName(obj.id, this.value) : null;
|
||||
callbacks.editCostItemName ? callbacks.editCostItemName(costItem.id, this.value) : null;
|
||||
}
|
||||
});
|
||||
|
||||
nameCell.style.paddingLeft = nestingLevel * 20 + "px";
|
||||
nameCell.appendChild(expandButton);
|
||||
nameCell.appendChild(nameInput);
|
||||
const widthButton = 20;
|
||||
nameInput.style.width = "calc(100% - " + widthButton + "%)";
|
||||
return nameCell;
|
||||
}
|
||||
|
||||
@@ -322,19 +450,19 @@ export class CostUI {
|
||||
return cell;
|
||||
}
|
||||
|
||||
static createTotalCostCell(obj) {
|
||||
static createTotalCostCell(costItem) {
|
||||
const totalCostCell = document.createElement("td");
|
||||
const totalCost = parseFloat(obj.TotalCost).toFixed(2);
|
||||
totalCostCell.textContent = obj.is_sum ? totalCost + " (Σ)" : totalCost;
|
||||
const totalCost = parseFloat(costItem.TotalCost).toFixed(2);
|
||||
totalCostCell.textContent = costItem.is_sum ? totalCost + " (Σ)" : totalCost;
|
||||
return totalCostCell;
|
||||
}
|
||||
|
||||
static createFlexContainerCell(obj, callbacks) {
|
||||
static createFlexContainerCell(costItem, callbacks) {
|
||||
const divFlex = document.createElement("div");
|
||||
divFlex.classList.add("flex-container");
|
||||
|
||||
const addButton = CostUI.createAddButton(obj, callbacks);
|
||||
const selectButton = CostUI.createSelectButton(obj, callbacks);
|
||||
const addButton = CostUI.createAddButton(costItem, callbacks);
|
||||
const selectButton = CostUI.createSelectButton(costItem, callbacks);
|
||||
|
||||
divFlex.appendChild(addButton);
|
||||
divFlex.appendChild(selectButton);
|
||||
@@ -344,102 +472,52 @@ export class CostUI {
|
||||
return flexContainerCell;
|
||||
}
|
||||
|
||||
static createAddButton(obj, callbacks) {
|
||||
static createAddButton(costItem, callbacks) {
|
||||
const addButton = document.createElement("button");
|
||||
addButton.textContent = "+";
|
||||
addButton.classList.add("add-button");
|
||||
addButton.addEventListener("click", function(e) {
|
||||
e.stopPropagation();
|
||||
callbacks.addCostItem ? callbacks.addCostItem(obj.id) : null;
|
||||
callbacks.addCostItem ? callbacks.addCostItem(costItem.id) : null;
|
||||
});
|
||||
return addButton;
|
||||
}
|
||||
|
||||
static createSelectButton(obj, callbacks) {
|
||||
static createSelectButton(costItem, callbacks) {
|
||||
const selectButton = document.createElement("button");
|
||||
selectButton.classList.add("action-button");
|
||||
selectButton.textContent = "Select";
|
||||
selectButton.addEventListener("click", function(e) {
|
||||
e.stopPropagation();
|
||||
callbacks.selectAssignedElements ? callbacks.selectAssignedElements(obj.id) : null;
|
||||
callbacks.selectAssignedElements ? callbacks.selectAssignedElements(costItem.id) : null;
|
||||
});
|
||||
return selectButton;
|
||||
}
|
||||
|
||||
static hideNestedRows(parentId) {
|
||||
const rows = document.querySelectorAll(`[parent-id='${parentId}']`);
|
||||
rows.forEach(row => {
|
||||
row.classList.add("nested");
|
||||
const childId = row.getAttribute('id');
|
||||
if (childId) {
|
||||
CostUI.hideNestedRows(childId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static showNestedRows(parentId) {
|
||||
const rows = document.querySelectorAll(`[parent-id='${parentId}']`);
|
||||
rows.forEach(row => {
|
||||
row.classList.remove("nested");
|
||||
const childId = row.getAttribute('id');
|
||||
if (childId && CostUI.isRowExpanded(childId)) {
|
||||
CostUI.showNestedRows(childId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static contractExpandRow(id) {
|
||||
const rows = document.querySelectorAll(`[parent-id='${id}']`);
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isVisible = false;
|
||||
rows.forEach(row => {
|
||||
if (!row.classList.contains("nested")) {
|
||||
isVisible = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (isVisible) {
|
||||
CostUI.hideNestedRows(id);
|
||||
this.textContent = ">";
|
||||
CostUI.updateExpandedState(id, false);
|
||||
} else {
|
||||
CostUI.showNestedRows(id);
|
||||
this.textContent = "^";
|
||||
CostUI.updateExpandedState(id, true);
|
||||
static highlightCostItem(id) {
|
||||
const row = document.getElementById(id);
|
||||
if (row) {
|
||||
row.classList.add("highlighted");
|
||||
}
|
||||
}
|
||||
|
||||
static updateExpandedState(id, isExpanded) {
|
||||
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || {};
|
||||
expandedState[id] = isExpanded;
|
||||
localStorage.setItem('expandedState', JSON.stringify(expandedState));
|
||||
static unhighlightCostItem(id) {
|
||||
const row = document.getElementById(id);
|
||||
if (row) {
|
||||
row.classList.remove("highlighted");
|
||||
}
|
||||
}
|
||||
|
||||
static applyExpandedState() {
|
||||
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || {};
|
||||
Object.keys(expandedState).forEach(id => {
|
||||
if (expandedState[id]) {
|
||||
CostUI.showNestedRows(id);
|
||||
const toggleButton = document.querySelector(`[id='${id}'] .toggle-button`);
|
||||
if (toggleButton) {
|
||||
toggleButton.textContent = "^";
|
||||
}
|
||||
} else {
|
||||
CostUI.hideNestedRows(id);
|
||||
const toggleButton = document.querySelector(`[id='${id}'] .toggle-button`);
|
||||
if (toggleButton) {
|
||||
toggleButton.textContent = ">";
|
||||
}
|
||||
}
|
||||
static getAllCostItemsIds() {
|
||||
const rows = document.querySelectorAll("#cost-items tr");
|
||||
const ids = [];
|
||||
rows.forEach(row => {
|
||||
ids.push(row.id);
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
static isRowExpanded(id) {
|
||||
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || {};
|
||||
return expandedState[id] || false;
|
||||
}
|
||||
|
||||
|
||||
static text(label) {
|
||||
const text = document.createElement("p");
|
||||
@@ -459,7 +537,7 @@ export class CostUI {
|
||||
cardTitle.textContent = title;
|
||||
|
||||
const cardButton = document.createElement("button");
|
||||
cardButton.classList.add("btn", "btn-primary");
|
||||
cardButton.classList.add("action-button");
|
||||
cardButton.textContent = "Load";
|
||||
cardButton.addEventListener("click", callback);
|
||||
|
||||
@@ -471,8 +549,15 @@ export class CostUI {
|
||||
return card;
|
||||
}
|
||||
|
||||
static getRowNameCell(costItemId) {
|
||||
return document.getElementById(costItemId).querySelector("td input");
|
||||
}
|
||||
|
||||
static getCostItemName(costItemId) {
|
||||
return CostUI.getRowNameCell(costItemId).value;
|
||||
}
|
||||
|
||||
static createCostValuesForm({ costItemId, costValues, callbacks }) {
|
||||
// Check if the form already exists and remove it if it does
|
||||
const formId = "cost-values-form-" + costItemId;
|
||||
let existingForm = document.getElementById(formId);
|
||||
if (existingForm) {
|
||||
@@ -480,14 +565,14 @@ export class CostUI {
|
||||
}
|
||||
|
||||
const form = CostUI.createFormElement(formId, "cost-values-form");
|
||||
|
||||
const header = CostUI.createHeader("Cost Values Form");
|
||||
const costItemName = CostUI.getCostItemName(costItemId);
|
||||
const header = CostUI.createHeader("Cost Values Form for " + costItemName);
|
||||
form.appendChild(header);
|
||||
|
||||
const table = CostUI.createCostValuesTable(costItemId, ["Type", "Category", "Value"]);
|
||||
const table = CostUI.createCostValuesTable(costItemId, ["Type", "Category", "Value", ""]);
|
||||
|
||||
costValues.forEach(costValue => {
|
||||
const tr = CostUI.createCostvaluesRow(costItemId, costValue);
|
||||
const tr = CostUI.createCostvaluesRow(costItemId, costValue, callbacks);
|
||||
table.appendChild(tr);
|
||||
});
|
||||
|
||||
@@ -496,9 +581,6 @@ export class CostUI {
|
||||
const addButton = CostUI.createAddCostValueButton(costItemId, callbacks);
|
||||
form.appendChild(addButton);
|
||||
|
||||
const submitButton = CostUI.createSubmitButton(callbacks);
|
||||
form.appendChild(submitButton);
|
||||
|
||||
const closeButton = CostUI.createCloseButton(form);
|
||||
form.appendChild(closeButton);
|
||||
|
||||
@@ -514,16 +596,15 @@ export class CostUI {
|
||||
return document.getElementById("cost-values-table-" + costItemId);
|
||||
}
|
||||
|
||||
static addNewCostValueRow(costItemId, costValueId) {
|
||||
static addNewCostValueRow(costItemId, costValueId, costValueCallbacks) {
|
||||
const table = CostUI.getCostValuesTable(costItemId);
|
||||
// check if table exists
|
||||
if (!table) {
|
||||
console.log("Cost values table not found for cost item ID:", costItemId);
|
||||
return;
|
||||
}
|
||||
console.log("Adding new cost value row", costValueId);
|
||||
console.log(table)
|
||||
const tr = CostUI.createCostvaluesRow(costItemId, {"category": "", "name": "", "applied_value": 0, "id": costValueId, "parent": costItemId});
|
||||
const tr = CostUI.createCostvaluesRow(costItemId, {"category": "", "name": "", "applied_value": 0, "id": costValueId, "parent": costItemId}, costValueCallbacks);
|
||||
table.appendChild(tr);
|
||||
}
|
||||
|
||||
@@ -543,19 +624,14 @@ export class CostUI {
|
||||
return table;
|
||||
}
|
||||
|
||||
static createCostvaluesRow(costItemId,costValue) {
|
||||
|
||||
static createCostvaluesRow(costItemId,costValue, costValueCallbacks) {
|
||||
function cleanLabel(value) {
|
||||
// remove anything which is not a dot or a digit
|
||||
return value.replace(/[^0-9.]/g, '');
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (document.getElementById(costValue.id)) {
|
||||
return document.getElementById(costValue.id);
|
||||
}
|
||||
|
||||
const tr = document.createElement("tr");
|
||||
tr.isCostValue = true;
|
||||
tr.parent = costItemId;
|
||||
@@ -577,6 +653,24 @@ export class CostUI {
|
||||
dropdown.addEventListener("change", function() {
|
||||
const selectedType = this.value;
|
||||
CostUI.updateRowBasedOnType(selectedType, categoryCell, valueCell1);
|
||||
|
||||
if (selectedType === "SUM") {
|
||||
const costValueId = parseInt(this.closest("tr").getAttribute("id"));
|
||||
const appliedValue = parseFloat(valueCell1.querySelector("input").value);
|
||||
const costCategory = categoryCell.querySelector("input").value;
|
||||
|
||||
if (costValueCallbacks.editCostValues) {
|
||||
costValueCallbacks.editCostValues(costItemId, [
|
||||
{
|
||||
costType: selectedType,
|
||||
costCategory: costCategory,
|
||||
appliedValue: appliedValue,
|
||||
id: costValueId,
|
||||
costItemId: costItemId
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
tr.appendChild(typeCell);
|
||||
|
||||
@@ -593,9 +687,40 @@ export class CostUI {
|
||||
}
|
||||
const valueCell1 = CostUI.createTableInput("number", "value", value);
|
||||
tr.appendChild(valueCell1);
|
||||
// Apply initial state based on costType
|
||||
|
||||
const inputValue = valueCell1.querySelector("input");
|
||||
function handleCostValueChange(costItemId) {
|
||||
const costValueData = {
|
||||
costType: typeCell.querySelector("select").value,
|
||||
costCategory: categoryCell.querySelector("input").value,
|
||||
appliedValue: parseFloat(this.value),
|
||||
id: costValue.id,
|
||||
costItemId: costItemId
|
||||
}
|
||||
if (costValueCallbacks.editCostValues) {
|
||||
costValueCallbacks.editCostValues(costItemId, [costValueData]);
|
||||
}
|
||||
}
|
||||
|
||||
inputValue.addEventListener("change", handleCostValueChange.bind(inputValue, costItemId));
|
||||
|
||||
inputValue.addEventListener("keydown", function(event) {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
handleCostValueChange.call(this, costItemId);
|
||||
}
|
||||
});
|
||||
const deleteCell = document.createElement("td");
|
||||
tr.appendChild(deleteCell);
|
||||
const deleteButton = document.createElement("button");
|
||||
deleteButton.classList.add("action-button");
|
||||
deleteButton.textContent = "Delete";
|
||||
deleteButton.addEventListener("click", function() {
|
||||
costValueCallbacks.deleteCostValue(costItemId, costValue.id);
|
||||
tr.remove();
|
||||
});
|
||||
deleteCell.appendChild(deleteButton);
|
||||
CostUI.updateRowBasedOnType(costType, categoryCell, valueCell1);
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
@@ -650,8 +775,8 @@ export class CostUI {
|
||||
|
||||
static createAddCostValueButton(costItemId, callbacks) {
|
||||
const addButton = document.createElement("button");
|
||||
addButton.textContent = "+";
|
||||
addButton.classList.add("add-button");
|
||||
addButton.classList.add("action-button");
|
||||
addButton.textContent = " + Add Cost Value";
|
||||
addButton.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
callbacks.addCostValue ? callbacks.addCostValue(costItemId) : null;
|
||||
@@ -659,38 +784,11 @@ export class CostUI {
|
||||
return addButton;
|
||||
}
|
||||
|
||||
static createSubmitButton(callbacks) {
|
||||
const submitButton = document.createElement("button");
|
||||
submitButton.type = "submit";
|
||||
submitButton.textContent = "Save";
|
||||
|
||||
submitButton.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
const form = this.closest("form");
|
||||
const costValues = [];
|
||||
const rows = Array.from(form.querySelectorAll("tr")).filter(row => row.isCostValue);
|
||||
|
||||
rows.forEach(row => {
|
||||
const costCategory = row.querySelector("input[name='category']").value;
|
||||
const appliedValue = parseFloat(row.querySelector("input[name='value']").value);
|
||||
const costType = row.querySelector("select[name='type']").value;
|
||||
const id = parseInt(row.id);
|
||||
costValues.push({ costType, costCategory, appliedValue, id: id, costItemId: row.parent });
|
||||
});
|
||||
const costItemId = parseInt(form.id.split("-")[3]);
|
||||
console.log(form.id)
|
||||
console.log("Cost values to be saved:", costItemId);
|
||||
callbacks.editCostValues ? callbacks.editCostValues(costItemId, costValues) : null;
|
||||
form.remove();
|
||||
});
|
||||
return submitButton;
|
||||
}
|
||||
|
||||
static createFormElement(id, className) {
|
||||
const form = document.createElement("form");
|
||||
form.id = id;
|
||||
form.classList.add(className);
|
||||
form.style.position = "absolute"; // Set initial position to absolute
|
||||
form.style.position = "absolute";
|
||||
form.style.top = "50%";
|
||||
form.style.left = "50%";
|
||||
form.style.transform = "translate(-50%, -50%)";
|
||||
@@ -710,7 +808,10 @@ export class CostUI {
|
||||
closeButton.innerHTML = "×";
|
||||
closeButton.addEventListener("click", function() {
|
||||
form.remove();
|
||||
const costItemId = form.id.split("-")[3];
|
||||
CostUI.unhighlightCostItem(costItemId);
|
||||
});
|
||||
|
||||
return closeButton;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BlenderBIM Web UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/gantt.css" id="index-stylesheet" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/cost.css" id="index-stylesheet" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
id="tabulator-stylesheet"
|
||||
@@ -32,30 +32,6 @@
|
||||
<script type="module" defer src="./static/js/cost.js"></script>
|
||||
<style>
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.nested {
|
||||
display: none;
|
||||
}
|
||||
.expand-collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
.caret-cell {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
td {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.flex-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -73,7 +49,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a href="/costing" class="active"
|
||||
><i class="fa-solid fa-table"></i>Costing</a
|
||||
><i class="fa-solid fa-money-bill-trend-up"></i> Costing</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -364,6 +364,15 @@ class Web(bonsai.core.tool.Web):
|
||||
bpy.ops.bim.add_cost_item(cost_item=operator_data["costItemId"])
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "deleteCostItem":
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
|
||||
bpy.ops.bim.remove_cost_item(cost_item=operator_data["costItemId"])
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "duplicateCostItem":
|
||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
new_cost_item = ifcopenshell.api.cost.copy_cost_item(ifc_file, cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "editCostItemName":
|
||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||
tool.Ifc.run(
|
||||
@@ -391,18 +400,25 @@ class Web(bonsai.core.tool.Web):
|
||||
data_key="cost_value",
|
||||
event="cost_value"
|
||||
)
|
||||
if operator_data["type"] == "deleteCostValue":
|
||||
bpy.ops.bim.remove_cost_value(
|
||||
parent=operator_data["costItemId"],
|
||||
cost_value=operator_data["costValueId"]
|
||||
)
|
||||
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "editCostValues":
|
||||
cost_item_id = operator_data["costItemId"]
|
||||
print('Editing cost values', cost_item_id)
|
||||
print(cost_item_id)
|
||||
print(type(operator_data["costValues"]))
|
||||
cost_item = ifc_file.by_id(cost_item_id)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
for value_data in operator_data["costValues"] or []:
|
||||
print(value_data)
|
||||
print("editing cost value data", value_data)
|
||||
value = ifc_file.by_id(value_data["id"])
|
||||
print(value.get_info())
|
||||
if value_data["costType"] == "FIXED":
|
||||
attributes= {
|
||||
"AppliedValue": value_data["appliedValue"],
|
||||
"Category": None
|
||||
}
|
||||
elif value_data["costType"] == "CATEGORY":
|
||||
attributes= {
|
||||
@@ -418,12 +434,8 @@ class Web(bonsai.core.tool.Web):
|
||||
cost_value=value,
|
||||
attributes= attributes
|
||||
)
|
||||
print(value.get_info())
|
||||
tool.Cost.load_cost_schedule_tree()
|
||||
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||
if not cost_item:
|
||||
print("Cost item not found")
|
||||
return
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user