From c1423bfecaae66edec573156d70ddc739fd41d38 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Tue, 19 Nov 2019 15:28:30 +0800 Subject: [PATCH 01/24] Fix modal button still loading --- src/renderer/components/Project/NewProjectModal.js | 1 + src/renderer/components/Project/OpenProjectModal.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/renderer/components/Project/NewProjectModal.js b/src/renderer/components/Project/NewProjectModal.js index 21cc2b1..d151741 100644 --- a/src/renderer/components/Project/NewProjectModal.js +++ b/src/renderer/components/Project/NewProjectModal.js @@ -36,6 +36,7 @@ const NewProjectModal = ({ setVisible(false); } catch (err) { console.log(err.response); + } finally { setConfirmLoading(false); } } diff --git a/src/renderer/components/Project/OpenProjectModal.js b/src/renderer/components/Project/OpenProjectModal.js index 7db4dfc..b7c56fd 100644 --- a/src/renderer/components/Project/OpenProjectModal.js +++ b/src/renderer/components/Project/OpenProjectModal.js @@ -27,6 +27,7 @@ const OpenProjectModal = ({ setVisible(false); } catch (err) { console.log(err.response); + } finally { setConfirmLoading(false); } } From 19aae505b5bc78708da4ed6423434a06a7fbe114 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Tue, 19 Nov 2019 15:29:32 +0800 Subject: [PATCH 02/24] Update style for schedule editor error text --- src/renderer/components/InputEditor/ScheduleEditor.css | 8 ++++++++ src/renderer/components/InputEditor/ScheduleEditor.js | 9 +++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.css b/src/renderer/components/InputEditor/ScheduleEditor.css index 4cda7ab..0de72fe 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.css +++ b/src/renderer/components/InputEditor/ScheduleEditor.css @@ -40,6 +40,14 @@ .cea-schedule-no-data { grid-column: 1/-1; grid-row: 1/-1; + margin: auto; +} + +.cea-schedule-error { + grid-column: 1/-1; + grid-row: 1/-1; + overflow: auto; + text-align: center; height: 100%; width: 100%; } diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index fc97ebc..29d120e 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -4,9 +4,8 @@ import { fetchBuildingSchedule, setSelected } from '../../actions/inputEditor'; import Tabulator from 'tabulator-tables'; import 'tabulator-tables/dist/css/tabulator.min.css'; import './ScheduleEditor.css'; -import CenterSpinner from '../HomePage/CenterSpinner'; import { months_short } from '../../constants/months'; -import { Tabs, Spin, Modal, Button, Card } from 'antd'; +import { Tabs, Spin, Button, Card } from 'antd'; const ScheduleEditor = () => { const { selected, tables } = useSelector(state => state.inputData); @@ -33,6 +32,7 @@ const ScheduleEditor = () => { } }; + // Initialize table useEffect(() => { tabulator.current = new Tabulator(divRef.current, { data: buildings.map(building => ({ Name: building })), @@ -83,10 +83,7 @@ const ScheduleEditor = () => {
{buildings.includes(selected[0]) ? ( Object.keys(errors).length ? ( -
+
ERRORS FOUND: {Object.keys(errors).map(building => (
Date: Wed, 20 Nov 2019 15:06:20 +0800 Subject: [PATCH 03/24] Add tooltips to column headers --- src/renderer/components/InputEditor/Table.js | 49 +++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/renderer/components/InputEditor/Table.js b/src/renderer/components/InputEditor/Table.js index ec919a8..3491704 100644 --- a/src/renderer/components/InputEditor/Table.js +++ b/src/renderer/components/InputEditor/Table.js @@ -1,7 +1,8 @@ import React, { useEffect, useRef, useState } from 'react'; +import ReactDOM from 'react-dom'; import { useSelector, useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; -import { Card, Button, Modal, message } from 'antd'; +import { Card, Button, Modal, message, Tooltip } from 'antd'; import { setSelected, updateInputData, @@ -19,7 +20,7 @@ const useTableData = tab => { const columns = useSelector(state => state.inputData.columns); const [data, setData] = useState([]); - const [columnDef, setColumnDef] = useState([]); + const [columnDef, setColumnDef] = useState({ columns: [], description: [] }); const dispatch = useDispatch(); @@ -46,8 +47,13 @@ const useTableData = tab => { ...tables[tab][row] })); - const getColumnDef = () => - Object.keys(columns[tab]).map(column => { + const getColumnDef = () => { + let description = []; + let _columns = Object.keys(columns[tab]).map(column => { + description.push({ + description: columns[tab][column].description, + unit: columns[tab][column].unit + }); let columnDef = { title: column, field: column }; if (column === 'Name') { columnDef.frozen = true; @@ -62,7 +68,8 @@ const useTableData = tab => { } return columnDef; }); - + return { columns: _columns, description: description }; + }; useEffect(() => { setColumnDef(getColumnDef()); setData(getData()); @@ -89,7 +96,7 @@ const Table = ({ tab }) => { tabulator.current = new Tabulator(divRef.current, { data: data, index: 'Name', - columns: columnDef, + columns: columnDef.columns, layout: 'fitDataFill', height: '300px', validationFailed: cell => { @@ -116,7 +123,7 @@ const Table = ({ tab }) => { useEffect(() => { if (tabulator.current) { tabulator.current.setData([]); - tabulator.current.setColumns(columnDef); + tabulator.current.setColumns(columnDef.columns); } }, [columnDef]); @@ -125,6 +132,34 @@ const Table = ({ tab }) => { if (!tabulator.current.getData().length) { tabulator.current.setData(data); tabulator.current.selectRow(selected); + // Add tooltips to column headers on new data + document + .querySelectorAll('.tabulator-col-content') + .forEach((col, index) => { + const { description, unit } = columnDef.description[index]; + ReactDOM.render( + + {description} +
+ {unit} +
+ ) + } + getPopupContainer={() => { + return document.getElementsByClassName('ant-card-body')[0]; + }} + > +
+ {columnDef.columns[index].title} +
+
+ , + col + ); + }); } else tabulator.current.updateData(data); } }, [data]); From 28509fcd9212d575af117e0b0efe5ed07b18ecb5 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Wed, 20 Nov 2019 16:15:22 +0800 Subject: [PATCH 04/24] Update schedule editor logic for selected buildings --- .../components/InputEditor/ScheduleEditor.js | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index 29d120e..50bf4b2 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -46,25 +46,22 @@ const ScheduleEditor = () => { }, []); useEffect(() => { + tabulator.current && tabulator.current.deselectRow(); if (buildings.includes(selected[0])) { - if (tabulator.current) { - tabulator.current.deselectRow(); - tabulator.current.selectRow(selected); - tabulator.current.getFilters().length && - tabulator.current.setFilter('Name', 'in', selected); - } - if (selected.length) { - setLoading(true); - dispatch(fetchBuildingSchedule(selected)) - .catch(error => { - console.log(error); - setErrors(error); - }) - .finally(() => { - setLoading(false); - }); - } - } else tabulator.current && tabulator.current.deselectRow(); + tabulator.current && tabulator.current.selectRow(selected); + setLoading(true); + dispatch(fetchBuildingSchedule(selected)) + .catch(error => { + console.log(error); + setErrors(error); + }) + .finally(() => { + setLoading(false); + }); + } + tabulator.current && + tabulator.current.getFilters().length && + tabulator.current.setFilter('Name', 'in', selected); }, [selected]); if (!buildings.length) return
No buildings found
; From 2fe3a2c81ffa68aa8b210eeb2f654026118b4a65 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Wed, 20 Nov 2019 16:16:52 +0800 Subject: [PATCH 05/24] Add diff function for schedules table --- .../components/InputEditor/ScheduleEditor.js | 121 ++++++++++++++---- 1 file changed, 96 insertions(+), 25 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index 50bf4b2..1fccfa6 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -8,7 +8,7 @@ import { months_short } from '../../constants/months'; import { Tabs, Spin, Button, Card } from 'antd'; const ScheduleEditor = () => { - const { selected, tables } = useSelector(state => state.inputData); + const { selected, tables, schedules } = useSelector(state => state.inputData); const [tab, setTab] = useState(null); const [loading, setLoading] = useState(false); const [errors, setErrors] = useState({}); @@ -91,13 +91,21 @@ const ScheduleEditor = () => { ) : (
- +
- +
- +
) @@ -115,17 +123,10 @@ const ScheduleEditor = () => { ); }; -const DataTable = ({ selected, tab }) => { - const { schedules } = useSelector(state => state.inputData); +const DataTable = ({ selected, tab, schedules }) => { const tabulator = useRef(null); const divRef = useRef(null); - - const parseData = (building, tab) => { - const buildingSchedule = schedules[building]; - if (!buildingSchedule || !tab) return []; - const days = buildingSchedule.SCHEDULES[tab]; - return Object.keys(days).map(day => ({ DAY: day, ...days[day] })); - }; + const tooltipsRef = useRef({ selected, schedules, tab }); useEffect(() => { tabulator.current = new Tabulator(divRef.current, { @@ -136,25 +137,48 @@ const DataTable = ({ selected, tab }) => { ...[...Array(24).keys()].map(i => ({ title: i.toString(), field: i.toString(), - headerSort: false + headerSort: false, + formatter: cell => { + const value = cell.getValue(); + if (value == 'DIFF') { + cell.getElement().style.fontWeight = 'bold'; + cell.getElement().style.fontStyle = 'italic'; + } + return value; + } })) ], layoutColumnsOnNewData: true, - layout: 'fitDataFill' + layout: 'fitDataFill', + tooltips: cell => { + if (cell.getValue() == 'DIFF') { + let out = ''; + for (const building of tooltipsRef.current.selected) { + out += `${building} - ${ + tooltipsRef.current.schedules[building].SCHEDULES[ + tooltipsRef.current.tab + ][cell.getData().DAY][cell.getField()] + }\n`; + } + return out; + } + } }); }, []); useEffect(() => { - tabulator.current && tabulator.current.setData(parseData(selected[0], tab)); + tooltipsRef.current = { selected, schedules, tab }; + tabulator.current && + tabulator.current.setData(parseData(schedules, selected, tab)); }, [selected, schedules, tab]); return
; }; -const YearTable = ({ selected }) => { - const { schedules } = useSelector(state => state.inputData); +const YearTable = ({ selected, schedules }) => { const tabulator = useRef(null); const divRef = useRef(null); + const tooltipsRef = useRef({ selected, schedules }); useEffect(() => { console.log('render year'); @@ -166,10 +190,31 @@ const YearTable = ({ selected }) => { ...[...Array(12).keys()].map(i => ({ title: months_short[i], field: i.toString(), - headerSort: false + headerSort: false, + formatter: cell => { + const value = cell.getValue(); + if (value == 'DIFF') { + cell.getElement().style.fontWeight = 'bold'; + cell.getElement().style.fontStyle = 'italic'; + } + return value; + } })) ], - layout: 'fitDataFill' + layout: 'fitDataFill', + tooltips: cell => { + if (cell.getValue() == 'DIFF') { + let out = ''; + for (const building of tooltipsRef.current.selected) { + out += `${building} - ${ + tooltipsRef.current.schedules[building].MONTHLY_MULTIPLIER[ + cell.getField() + ] + }\n`; + } + return out; + } + } }); const redrawTable = () => { @@ -183,6 +228,7 @@ const YearTable = ({ selected }) => { }, []); useEffect(() => { + tooltipsRef.current = { selected, schedules }; tabulator.current && tabulator.current.setData(parseYearData(schedules, selected)); }, [schedules, selected]); @@ -190,8 +236,7 @@ const YearTable = ({ selected }) => { return
; }; -const ScheduleTab = ({ tab, setTab }) => { - const { schedules } = useSelector(state => state.inputData); +const ScheduleTab = ({ tab, setTab, schedules }) => { const TabPanes = getScheduleTypes(schedules).map(schedule => ( )); @@ -252,13 +297,31 @@ const parseYearData = (schedules, selected) => { !selected.every(building => Object.keys(schedules).includes(building)) ) return []; + let out = []; for (const building of selected) { const buildingSchedule = schedules[building].MONTHLY_MULTIPLIER; + out = diffArray(out, buildingSchedule); } + console.log(out); + return [{ name: 'MONTHLY_MULTIPLIER', ...out }]; +}; - return [ - { name: 'MONTHLY_MULTIPLIER', ...schedules[selected[0]].MONTHLY_MULTIPLIER } - ]; +const parseData = (schedules, selected, tab) => { + if ( + !tab || + !selected.length || + !selected.every(building => Object.keys(schedules).includes(building)) + ) + return []; + let out = {}; + for (const building of selected) { + const days = schedules[building].SCHEDULES[tab]; + for (const day of Object.keys(days)) { + out[day] = diffArray(out[day], days[day]); + } + } + console.log(out); + return Object.keys(out).map(day => ({ DAY: day, ...out[day] })); }; const getScheduleTypes = schedules => { @@ -272,4 +335,12 @@ const getScheduleTypes = schedules => { } }; +const diffArray = (first, second) => { + if (typeof first == 'undefined' || !first.length) return second; + return first.map((value, index) => { + if (value == second[index]) return value; + return 'DIFF'; + }); +}; + export default ScheduleEditor; From dbf8edf9bba45c692162fb3d3313b412b7c34180 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Wed, 20 Nov 2019 19:02:38 +0800 Subject: [PATCH 06/24] Fix discard changes message popup too high --- src/renderer/components/InputEditor/Table.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/components/InputEditor/Table.js b/src/renderer/components/InputEditor/Table.js index 3491704..e3042ce 100644 --- a/src/renderer/components/InputEditor/Table.js +++ b/src/renderer/components/InputEditor/Table.js @@ -223,6 +223,9 @@ const Table = ({ tab }) => { await dispatch(discardChanges()) .then(data => { console.log(data); + message.config({ + top: 120 + }); message.info('Unsaved changes have been discarded.'); }) .catch(error => { From a14de47401c4970d69d8eba1759747d03b7e2705 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Thu, 21 Nov 2019 11:56:45 +0800 Subject: [PATCH 07/24] Refactor input editor to share table buttons --- .../components/InputEditor/InputEditor.js | 3 +- .../components/InputEditor/ScheduleEditor.js | 238 ++++++----- src/renderer/components/InputEditor/Table.js | 391 ++++++++++-------- 3 files changed, 330 insertions(+), 302 deletions(-) diff --git a/src/renderer/components/InputEditor/InputEditor.js b/src/renderer/components/InputEditor/InputEditor.js index d3b2a14..f67db0b 100644 --- a/src/renderer/components/InputEditor/InputEditor.js +++ b/src/renderer/components/InputEditor/InputEditor.js @@ -8,7 +8,6 @@ import CenterSpinner from '../HomePage/CenterSpinner'; import NavigationPrompt from './NavigationPrompt'; import { withErrorBoundary } from '../../utils/ErrorBoundary'; import './InputEditor.css'; -import ScheduleEditor from './ScheduleEditor'; const MAP_STYLE = { height: '500px', @@ -78,7 +77,7 @@ const InputTable = () => { {TabPanes}
- {tab != 'schedules' ? : } +
); diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index 1fccfa6..341b375 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -1,20 +1,24 @@ import React, { useEffect, useRef, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { fetchBuildingSchedule, setSelected } from '../../actions/inputEditor'; +import { + fetchBuildingSchedule, + setSelected, + updateDaySchedule, + updateYearSchedule +} from '../../actions/inputEditor'; import Tabulator from 'tabulator-tables'; import 'tabulator-tables/dist/css/tabulator.min.css'; import './ScheduleEditor.css'; import { months_short } from '../../constants/months'; -import { Tabs, Spin, Button, Card } from 'antd'; +import { Tabs, Spin } from 'antd'; -const ScheduleEditor = () => { - const { selected, tables, schedules } = useSelector(state => state.inputData); +const ScheduleEditor = ({ selected, schedules, tabulator }) => { + const { tables } = useSelector(state => state.inputData); const [tab, setTab] = useState(null); const [loading, setLoading] = useState(false); const [errors, setErrors] = useState({}); const buildings = Object.keys(tables.zone || {}); const dispatch = useDispatch(); - const tabulator = useRef(null); const divRef = useRef(null); const selectRow = (e, cell) => { @@ -34,17 +38,27 @@ const ScheduleEditor = () => { // Initialize table useEffect(() => { + const filtered = tabulator.current && tabulator.current.getFilters().length; tabulator.current = new Tabulator(divRef.current, { - data: buildings.map(building => ({ Name: building })), + data: buildings.sort().map(building => ({ Name: building })), index: 'Name', columns: [{ title: 'Name', field: 'Name' }], layout: 'fitColumns', height: '300px', cellClick: selectRow }); - tabulator.current.setSort('Name', 'asc'); + filtered && tabulator.current.setFilter('Name', 'in', selected); }, []); + useEffect(() => { + const buildings = Object.keys(tables.zone || {}); + tabulator.current && + tabulator.current.replaceData( + buildings.sort().map(building => ({ Name: building })) + ); + tabulator.current.redraw(); + }, [tables]); + useEffect(() => { tabulator.current && tabulator.current.deselectRow(); if (buildings.includes(selected[0])) { @@ -67,59 +81,53 @@ const ScheduleEditor = () => { if (!buildings.length) return
No buildings found
; return ( - } - > -
-
-
-
- -
- {buildings.includes(selected[0]) ? ( - Object.keys(errors).length ? ( -
- ERRORS FOUND: - {Object.keys(errors).map(building => ( -
{`${building}: ${errors[building].message}`}
- ))} -
- ) : ( - -
- -
-
- -
-
- -
-
- ) - ) : ( -
- {selected.length - ? 'Selected buildings do not have a schedule' - : 'No building selected'} -
- )} -
-
+
+
+
- + +
+ {buildings.includes(selected[0]) ? ( + Object.keys(errors).length ? ( +
+ ERRORS FOUND: + {Object.keys(errors).map(building => ( +
{`${building}: ${errors[building].message}`}
+ ))} +
+ ) : ( + +
+ +
+
+ +
+
+ +
+
+ ) + ) : ( +
+ {selected.length + ? 'Selected buildings do not have a schedule' + : 'No building selected'} +
+ )} +
+
+
); }; @@ -127,6 +135,7 @@ const DataTable = ({ selected, tab, schedules }) => { const tabulator = useRef(null); const divRef = useRef(null); const tooltipsRef = useRef({ selected, schedules, tab }); + const dispatch = useDispatch(); useEffect(() => { tabulator.current = new Tabulator(divRef.current, { @@ -138,22 +147,33 @@ const DataTable = ({ selected, tab, schedules }) => { title: i.toString(), field: i.toString(), headerSort: false, + editor: 'input', + // Hack to allow editing when double clicking + cellDblClick: () => {}, formatter: cell => { - const value = cell.getValue(); - if (value == 'DIFF') { - cell.getElement().style.fontWeight = 'bold'; - cell.getElement().style.fontStyle = 'italic'; - } - return value; + formatCellStyle(cell); + return cell.getValue(); } })) ], + cellEdited: cell => { + formatCellStyle(cell); + dispatch( + updateDaySchedule( + tooltipsRef.current.selected, + tooltipsRef.current.tab, + cell.getData().DAY, + cell.getField(), + cell.getValue() + ) + ); + }, layoutColumnsOnNewData: true, layout: 'fitDataFill', tooltips: cell => { if (cell.getValue() == 'DIFF') { let out = ''; - for (const building of tooltipsRef.current.selected) { + for (const building of tooltipsRef.current.selected.sort()) { out += `${building} - ${ tooltipsRef.current.schedules[building].SCHEDULES[ tooltipsRef.current.tab @@ -169,6 +189,9 @@ const DataTable = ({ selected, tab, schedules }) => { useEffect(() => { tooltipsRef.current = { selected, schedules, tab }; tabulator.current && + tab && + selected.length && + selected.every(building => Object.keys(schedules).includes(building)) && tabulator.current.setData(parseData(schedules, selected, tab)); }, [selected, schedules, tab]); @@ -179,11 +202,12 @@ const YearTable = ({ selected, schedules }) => { const tabulator = useRef(null); const divRef = useRef(null); const tooltipsRef = useRef({ selected, schedules }); + const dispatch = useDispatch(); useEffect(() => { console.log('render year'); tabulator.current = new Tabulator(divRef.current, { - data: parseYearData(schedules, selected), + data: [], index: 'name', columns: [ { title: '', field: 'name', headerSort: false }, @@ -191,21 +215,30 @@ const YearTable = ({ selected, schedules }) => { title: months_short[i], field: i.toString(), headerSort: false, + editor: 'input', + // Hack to allow editing when double clicking + cellDblClick: () => {}, formatter: cell => { - const value = cell.getValue(); - if (value == 'DIFF') { - cell.getElement().style.fontWeight = 'bold'; - cell.getElement().style.fontStyle = 'italic'; - } - return value; + formatCellStyle(cell); + return cell.getValue(); } })) ], + cellEdited: cell => { + formatCellStyle(cell); + dispatch( + updateYearSchedule( + tooltipsRef.current.selected, + cell.getField(), + cell.getValue() + ) + ); + }, layout: 'fitDataFill', tooltips: cell => { if (cell.getValue() == 'DIFF') { let out = ''; - for (const building of tooltipsRef.current.selected) { + for (const building of tooltipsRef.current.selected.sort()) { out += `${building} - ${ tooltipsRef.current.schedules[building].MONTHLY_MULTIPLIER[ cell.getField() @@ -230,6 +263,8 @@ const YearTable = ({ selected, schedules }) => { useEffect(() => { tooltipsRef.current = { selected, schedules }; tabulator.current && + selected.length && + selected.every(building => Object.keys(schedules).includes(building)) && tabulator.current.setData(parseYearData(schedules, selected)); }, [schedules, selected]); @@ -253,50 +288,17 @@ const ScheduleTab = ({ tab, setTab, schedules }) => { ); }; -const TableButtons = ({ selected, tabulator }) => { - const [filterToggle, setFilterToggle] = useState(false); - const dispatch = useDispatch(); - - const selectAll = () => { - dispatch(setSelected(tabulator.current.getData().map(data => data.Name))); - }; - - const filterSelected = () => { - if (filterToggle) { - tabulator.current.clearFilter(); - } else { - tabulator.current.setFilter('Name', 'in', selected); - } - tabulator.current.redraw(); - setFilterToggle(oldValue => !oldValue); - }; - - const clearSelected = () => { - dispatch(setSelected([])); - }; - - return ( -
- - - {selected.length ? ( - - ) : null} -
- ); +const formatCellStyle = cell => { + if (cell.getValue() == 'DIFF') { + cell.getElement().style.fontWeight = 'bold'; + cell.getElement().style.fontStyle = 'italic'; + } else { + cell.getElement().style.fontWeight = 'normal'; + cell.getElement().style.fontStyle = 'normal'; + } }; const parseYearData = (schedules, selected) => { - if ( - !selected.length || - !selected.every(building => Object.keys(schedules).includes(building)) - ) - return []; let out = []; for (const building of selected) { const buildingSchedule = schedules[building].MONTHLY_MULTIPLIER; @@ -307,12 +309,6 @@ const parseYearData = (schedules, selected) => { }; const parseData = (schedules, selected, tab) => { - if ( - !tab || - !selected.length || - !selected.every(building => Object.keys(schedules).includes(building)) - ) - return []; let out = {}; for (const building of selected) { const days = schedules[building].SCHEDULES[tab]; diff --git a/src/renderer/components/InputEditor/Table.js b/src/renderer/components/InputEditor/Table.js index e3042ce..ed2e30a 100644 --- a/src/renderer/components/InputEditor/Table.js +++ b/src/renderer/components/InputEditor/Table.js @@ -14,164 +14,42 @@ import EditSelectedModal from './EditSelectedModal'; import routes from '../../constants/routes'; import Tabulator from 'tabulator-tables'; import 'tabulator-tables/dist/css/tabulator.min.css'; +import ScheduleEditor from './ScheduleEditor'; -const useTableData = tab => { - const tables = useSelector(state => state.inputData.tables); - const columns = useSelector(state => state.inputData.columns); - - const [data, setData] = useState([]); - const [columnDef, setColumnDef] = useState({ columns: [], description: [] }); - - const dispatch = useDispatch(); - - const selectRow = (e, cell) => { - const row = cell.getRow(); - const selectedRows = cell - .getTable() - .getSelectedData() - .map(data => data.Name); - if (cell.getRow().isSelected()) { - dispatch( - setSelected(selectedRows.filter(name => name !== row.getIndex())) - ); - } else { - dispatch(setSelected([...selectedRows, row.getIndex()])); - } - }; - - const getData = () => - Object.keys(tables[tab]) - .sort((a, b) => (a > b ? 1 : -1)) - .map(row => ({ - Name: row, - ...tables[tab][row] - })); - - const getColumnDef = () => { - let description = []; - let _columns = Object.keys(columns[tab]).map(column => { - description.push({ - description: columns[tab][column].description, - unit: columns[tab][column].unit - }); - let columnDef = { title: column, field: column }; - if (column === 'Name') { - columnDef.frozen = true; - columnDef.cellClick = selectRow; - } else if (column !== 'REFERENCE') { - columnDef.editor = 'input'; - columnDef.validator = - columns[tab][column].type === 'str' ? 'string' : 'numeric'; - columnDef.minWidth = 100; - // Hack to allow editing when double clicking - columnDef.cellDblClick = () => {}; - } - return columnDef; - }); - return { columns: _columns, description: description }; - }; - useEffect(() => { - setColumnDef(getColumnDef()); - setData(getData()); - }, [tab]); - - useEffect(() => { - setData(getData()); - }, [tables[tab]]); +const Table = ({ tab }) => { + const { selected, changes, schedules } = useSelector( + state => state.inputData + ); + const tabulator = useRef(null); - return [data, columnDef]; + return ( + + + } + > + {tab == 'schedules' ? ( + + ) : ( + + )} + + + + ); }; -const Table = ({ tab }) => { - const [data, columnDef] = useTableData(tab); - const { selected, changes } = useSelector(state => state.inputData); +const InputEditorButtons = ({ changes }) => { + const dispatch = useDispatch(); const noChanges = !Object.keys(changes.update).length && !Object.keys(changes.delete).length; - const dispatch = useDispatch(); - const tableRef = useRef(tab); - const tabulator = useRef(null); - const divRef = useRef(null); - - useEffect(() => { - tabulator.current = new Tabulator(divRef.current, { - data: data, - index: 'Name', - columns: columnDef.columns, - layout: 'fitDataFill', - height: '300px', - validationFailed: cell => { - cell.cancelEdit(); - }, - cellEdited: cell => { - dispatch( - updateInputData( - tableRef.current, - [cell.getData()['Name']], - [{ property: cell.getField(), value: cell.getValue() }] - ) - ); - }, - placeholder: '
No matching records found.
' - }); - }, []); - - // Keep reference of current table name - useEffect(() => { - tableRef.current = tab; - }, [tab]); - - useEffect(() => { - if (tabulator.current) { - tabulator.current.setData([]); - tabulator.current.setColumns(columnDef.columns); - } - }, [columnDef]); - - useEffect(() => { - if (tabulator.current) { - if (!tabulator.current.getData().length) { - tabulator.current.setData(data); - tabulator.current.selectRow(selected); - // Add tooltips to column headers on new data - document - .querySelectorAll('.tabulator-col-content') - .forEach((col, index) => { - const { description, unit } = columnDef.description[index]; - ReactDOM.render( - - {description} -
- {unit} -
- ) - } - getPopupContainer={() => { - return document.getElementsByClassName('ant-card-body')[0]; - }} - > -
- {columnDef.columns[index].title} -
-
- , - col - ); - }); - } else tabulator.current.updateData(data); - } - }, [data]); - - useEffect(() => { - if (tabulator.current) { - tabulator.current.deselectRow(); - tabulator.current.selectRow(selected); - tabulator.current.getFilters().length && - tabulator.current.setFilter('Name', 'in', selected); - } - }, [selected]); const _saveChanges = () => { Modal.confirm({ @@ -238,28 +116,6 @@ const Table = ({ tab }) => { return ( - - } - > -
- {!data.length ? ( -
- Input file could not be found. You can create the file using - {tab == 'surroundings' ? ( - - {' surroundings-helper '} - - ) : ( - {' data-helper '} - )} - tool. -
- ) : null} - @@ -328,7 +184,7 @@ const ChangesSummary = ({ changes }) => { ); }; -const TableButtons = ({ selected, tabulator, table }) => { +const TableButtons = ({ selected, tabulator, tab }) => { const [filterToggle, setFilterToggle] = useState(false); const [selectedInTable, setSelectedInTable] = useState(true); const [modalVisible, setModalVisible] = useState(false); @@ -336,9 +192,9 @@ const TableButtons = ({ selected, tabulator, table }) => { const dispatch = useDispatch(); useEffect(() => { - const tableData = data[table] || {}; + const tableData = data[tab] || tab == 'schedules' ? data['zone'] : {}; setSelectedInTable(Object.keys(tableData).includes(selected[0])); - }, [table, selected]); + }, [tab, selected]); const selectAll = () => { dispatch(setSelected(tabulator.current.getData().map(data => data.Name))); @@ -350,6 +206,7 @@ const TableButtons = ({ selected, tabulator, table }) => { } else { tabulator.current.setFilter('Name', 'in', selected); } + tabulator.current.redraw(); setFilterToggle(oldValue => !oldValue); }; @@ -395,7 +252,9 @@ const TableButtons = ({ selected, tabulator, table }) => { {selectedInTable ? ( - + {tab != 'schedules' && ( + + )}
); }; +const TableEditor = ({ tab, selected, tabulator }) => { + const [data, columnDef] = useTableData(tab); + const dispatch = useDispatch(); + const divRef = useRef(null); + const tableRef = useRef(tab); + + useEffect(() => { + const filtered = tabulator.current && tabulator.current.getFilters().length; + tabulator.current = new Tabulator(divRef.current, { + data: data, + index: 'Name', + columns: columnDef.columns, + layout: 'fitDataFill', + height: '300px', + validationFailed: cell => { + cell.cancelEdit(); + }, + cellEdited: cell => { + dispatch( + updateInputData( + tableRef.current, + [cell.getData()['Name']], + [{ property: cell.getField(), value: cell.getValue() }] + ) + ); + }, + placeholder: '
No matching records found.
' + }); + filtered && tabulator.current.setFilter('Name', 'in', selected); + }, []); + + // Keep reference of current table name + useEffect(() => { + tableRef.current = tab; + }, [tab]); + + useEffect(() => { + if (tabulator.current) { + tabulator.current.setData([]); + tabulator.current.setColumns(columnDef.columns); + } + }, [columnDef]); + + useEffect(() => { + if (tabulator.current) { + if (!tabulator.current.getData().length) { + tabulator.current.setData(data); + tabulator.current.selectRow(selected); + // Add tooltips to column headers on new data + document + .querySelectorAll('.tabulator-col-content') + .forEach((col, index) => { + const { description, unit } = columnDef.description[index]; + ReactDOM.render( + + {description} +
+ {unit} +
+ ) + } + getPopupContainer={() => { + return document.getElementsByClassName('ant-card-body')[0]; + }} + > +
+ {columnDef.columns[index].title} +
+
+ , + col + ); + }); + } else tabulator.current.replaceData(data); + } + }, [data]); + + useEffect(() => { + if (tabulator.current) { + tabulator.current.deselectRow(); + tabulator.current.selectRow(selected); + tabulator.current.getFilters().length && + tabulator.current.setFilter('Name', 'in', selected); + } + }, [selected]); + + return ( + +
+ {!data.length ? ( +
+ Input file could not be found. You can create the file using + {tab == 'surroundings' ? ( + + {' surroundings-helper '} + + ) : ( + {' data-helper '} + )} + tool. +
+ ) : null} + + ); +}; + +const useTableData = tab => { + const { columns, tables } = useSelector(state => state.inputData); + const [data, setData] = useState([]); + const [columnDef, setColumnDef] = useState({ columns: [], description: [] }); + + const dispatch = useDispatch(); + + const selectRow = (e, cell) => { + const row = cell.getRow(); + const selectedRows = cell + .getTable() + .getSelectedData() + .map(data => data.Name); + if (cell.getRow().isSelected()) { + dispatch( + setSelected(selectedRows.filter(name => name !== row.getIndex())) + ); + } else { + dispatch(setSelected([...selectedRows, row.getIndex()])); + } + }; + + const getData = () => + Object.keys(tables[tab]) + .sort() + .map(row => ({ + Name: row, + ...tables[tab][row] + })); + + const getColumnDef = () => { + let description = []; + let _columns = Object.keys(columns[tab]).map(column => { + description.push({ + description: columns[tab][column].description, + unit: columns[tab][column].unit + }); + let columnDef = { title: column, field: column }; + if (column === 'Name') { + columnDef.frozen = true; + columnDef.cellClick = selectRow; + } else if (column !== 'REFERENCE') { + columnDef.editor = 'input'; + columnDef.validator = + columns[tab][column].type === 'str' ? 'string' : 'numeric'; + columnDef.minWidth = 100; + // Hack to allow editing when double clicking + columnDef.cellDblClick = () => {}; + } + return columnDef; + }); + return { columns: _columns, description: description }; + }; + useEffect(() => { + setColumnDef(getColumnDef()); + setData(getData()); + }, [tab]); + + useEffect(() => { + setData(getData()); + }, [tables[tab]]); + + return [data, columnDef]; +}; + export default Table; From 68507debdc030b1a67894533c3ea6d5594dfea31 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Thu, 21 Nov 2019 14:33:58 +0800 Subject: [PATCH 08/24] Update data of table instead of replacing when data changes --- src/renderer/components/InputEditor/ScheduleEditor.js | 6 ++---- src/renderer/components/InputEditor/Table.js | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index 341b375..e2c5ede 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -192,7 +192,7 @@ const DataTable = ({ selected, tab, schedules }) => { tab && selected.length && selected.every(building => Object.keys(schedules).includes(building)) && - tabulator.current.setData(parseData(schedules, selected, tab)); + tabulator.current.updateOrAddData(parseData(schedules, selected, tab)); }, [selected, schedules, tab]); return
; @@ -265,7 +265,7 @@ const YearTable = ({ selected, schedules }) => { tabulator.current && selected.length && selected.every(building => Object.keys(schedules).includes(building)) && - tabulator.current.setData(parseYearData(schedules, selected)); + tabulator.current.updateOrAddData(parseYearData(schedules, selected)); }, [schedules, selected]); return
; @@ -304,7 +304,6 @@ const parseYearData = (schedules, selected) => { const buildingSchedule = schedules[building].MONTHLY_MULTIPLIER; out = diffArray(out, buildingSchedule); } - console.log(out); return [{ name: 'MONTHLY_MULTIPLIER', ...out }]; }; @@ -316,7 +315,6 @@ const parseData = (schedules, selected, tab) => { out[day] = diffArray(out[day], days[day]); } } - console.log(out); return Object.keys(out).map(day => ({ DAY: day, ...out[day] })); }; diff --git a/src/renderer/components/InputEditor/Table.js b/src/renderer/components/InputEditor/Table.js index ed2e30a..529e94a 100644 --- a/src/renderer/components/InputEditor/Table.js +++ b/src/renderer/components/InputEditor/Table.js @@ -347,7 +347,9 @@ const TableEditor = ({ tab, selected, tabulator }) => { col ); }); - } else tabulator.current.replaceData(data); + } else if (tabulator.current.getData().length == data.length) { + tabulator.current.updateData(data); + } else tabulator.current.setData(data); } }, [data]); From e4f33f9e5fec24c06c0ab735c1aa091e1a8528f3 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Thu, 21 Nov 2019 14:53:13 +0800 Subject: [PATCH 09/24] Rewrite updateChanges into a function --- src/renderer/components/InputEditor/Table.js | 2 +- src/renderer/reducers/inputEditor.js | 67 +++++++++++++------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/renderer/components/InputEditor/Table.js b/src/renderer/components/InputEditor/Table.js index 529e94a..cdc2a80 100644 --- a/src/renderer/components/InputEditor/Table.js +++ b/src/renderer/components/InputEditor/Table.js @@ -167,7 +167,7 @@ const ChangesSummary = ({ changes }) => { property => (
{property} - {`: ${changes.update[table][building][property].oldValue} + {` : ${changes.update[table][building][property].oldValue} → ${changes.update[table][building][property].newValue}`}
diff --git a/src/renderer/reducers/inputEditor.js b/src/renderer/reducers/inputEditor.js index 3981f7c..cf858fa 100644 --- a/src/renderer/reducers/inputEditor.js +++ b/src/renderer/reducers/inputEditor.js @@ -32,32 +32,48 @@ function createNestedProp(obj, prop, ...rest) { return createNestedProp(obj[prop], ...rest); } +function updateChanges( + changes, + table, + building, + property, + storedValue, + newValue +) { + if (createNestedProp(changes.update, table, building, property, 'oldValue')) { + // Delete update if newValue equals oldValue else update newValue + if (changes.update[table][building][property].oldValue == newValue) { + delete changes.update[table][building][property]; + // Delete update building entry if it is empty + if (!Object.keys(changes.update[table][building]).length) { + delete changes.update[table][building]; + if (!Object.keys(changes.update[table]).length) + delete changes.update[table]; + } + } else changes.update[table][building][property].newValue = newValue; + } else { + // Store old and new value + changes.update[table][building][property] = { + oldValue: storedValue, + newValue: newValue + }; + } +} + function updateData(state, table, buildings, properties) { let { geojsons, tables, changes } = state; for (const building of buildings) { - for (const _property of properties) { - const { property, value } = _property; + for (const propertyObj of properties) { + const { property, value } = propertyObj; // Track update changes - if ( - createNestedProp(changes.update, table, building, property, 'oldValue') - ) { - // Delete update if newValue equals oldValue else update newValue - if (changes.update[table][building][property].oldValue == value) { - delete changes.update[table][building][property]; - // Delete update building entry if it is empty - if (!Object.keys(changes.update[table][building]).length) { - delete changes.update[table][building]; - if (!Object.keys(changes.update[table]).length) - delete changes.update[table]; - } - } else changes.update[table][building][property].newValue = value; - } else { - // Store old and new value - changes.update[table][building][property] = { - oldValue: tables[table][building][property], - newValue: value - }; - } + updateChanges( + changes, + table, + building, + property, + tables[table][building][property], + value + ); tables = { ...tables, @@ -75,7 +91,12 @@ function updateData(state, table, buildings, properties) { // Update building properties of geojsons if (buildingGeometries.includes(table)) { - geojsons = updateGeoJsonProperty(geojsons, table, building, _property); + geojsons = updateGeoJsonProperty( + geojsons, + table, + building, + propertyObj + ); } } } From 57043258404cf83eedb66aa791ef0ff181fdafe7 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Thu, 21 Nov 2019 16:46:00 +0800 Subject: [PATCH 10/24] Add basic input validation for year table --- src/renderer/components/InputEditor/ScheduleEditor.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index e2c5ede..e4571f5 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -56,6 +56,7 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { tabulator.current.replaceData( buildings.sort().map(building => ({ Name: building })) ); + tabulator.current.selectRow(selected); tabulator.current.redraw(); }, [tables]); @@ -156,6 +157,9 @@ const DataTable = ({ selected, tab, schedules }) => { } })) ], + validationFailed: cell => { + cell.cancelEdit(); + }, cellEdited: cell => { formatCellStyle(cell); dispatch( @@ -205,7 +209,6 @@ const YearTable = ({ selected, schedules }) => { const dispatch = useDispatch(); useEffect(() => { - console.log('render year'); tabulator.current = new Tabulator(divRef.current, { data: [], index: 'name', @@ -216,6 +219,7 @@ const YearTable = ({ selected, schedules }) => { field: i.toString(), headerSort: false, editor: 'input', + validator: ['max:1', 'min:0'], // Hack to allow editing when double clicking cellDblClick: () => {}, formatter: cell => { @@ -224,6 +228,9 @@ const YearTable = ({ selected, schedules }) => { } })) ], + validationFailed: cell => { + cell.cancelEdit(); + }, cellEdited: cell => { formatCellStyle(cell); dispatch( From 8479e2de36ea0c6edbe01fa60723c0e050965423 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Thu, 21 Nov 2019 16:47:19 +0800 Subject: [PATCH 11/24] Adjust column width when selecting new buildings and changing tab --- src/renderer/components/InputEditor/ScheduleEditor.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index e4571f5..d164dfe 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -199,6 +199,10 @@ const DataTable = ({ selected, tab, schedules }) => { tabulator.current.updateOrAddData(parseData(schedules, selected, tab)); }, [selected, schedules, tab]); + useEffect(() => { + tabulator.current.redraw(true); + }, [selected, tab]); + return
; }; From 9aa895588992048c2398f4efda0998820a126f57 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Thu, 21 Nov 2019 16:59:10 +0800 Subject: [PATCH 12/24] Add schedule editor redux actions --- src/renderer/actions/inputEditor.js | 18 ++++++ src/renderer/reducers/inputEditor.js | 83 ++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/src/renderer/actions/inputEditor.js b/src/renderer/actions/inputEditor.js index 747c0b6..74fa1f1 100644 --- a/src/renderer/actions/inputEditor.js +++ b/src/renderer/actions/inputEditor.js @@ -15,6 +15,8 @@ export const REQUEST_MAPDATA = 'REQUEST_MAPDATA'; export const RECEIVE_MAPDATA = 'RECEIVE_MAPDATA'; export const SET_SELECTED = 'SET_SELECTED'; export const UPDATE_INPUTDATA = 'UPDATE_INPUTDATA'; +export const UPDATE_YEARSCHEDULE = 'UPDATE_YEARSCHEDULE'; +export const UPDATE_DAYSCHEDULE = 'UPDATE_DAYSCHEDULE'; export const DELETE_BUILDINGS = 'DELETE_BUILDINGS'; export const SAVE_INPUTDATA = 'SAVE_INPUTDATA'; export const SAVE_INPUTDATA_SUCCESS = 'SAVE_INPUTDATA_SUCCESS'; @@ -111,6 +113,22 @@ export const updateInputData = ( payload: { table, buildings, properties } }); +export const updateYearSchedule = (buildings = [], month = '', value = 0) => ({ + type: UPDATE_YEARSCHEDULE, + payload: { buildings, month, value } +}); + +export const updateDaySchedule = ( + buildings = [], + tab = '', + day = '', + hour = 0, + value = '' +) => ({ + type: UPDATE_DAYSCHEDULE, + payload: { buildings, tab, day, hour, value } +}); + export const deleteBuildings = (buildings = []) => ({ type: DELETE_BUILDINGS, payload: { buildings } diff --git a/src/renderer/reducers/inputEditor.js b/src/renderer/reducers/inputEditor.js index cf858fa..9590aa4 100644 --- a/src/renderer/reducers/inputEditor.js +++ b/src/renderer/reducers/inputEditor.js @@ -9,10 +9,13 @@ import { RESET_INPUTDATA, SET_SELECTED, UPDATE_INPUTDATA, + UPDATE_YEARSCHEDULE, + UPDATE_DAYSCHEDULE, DELETE_BUILDINGS, SAVE_INPUTDATA_SUCCESS, DISCARD_INPUTDATA_CHANGES_SUCCESS } from '../actions/inputEditor'; +import { months_short } from '../constants/months'; const initialState = { selected: [], @@ -166,6 +169,64 @@ function deleteGeoJsonFeature(geojsons, table, building) { }; } +function updateDaySchedule(state, buildings, tab, day, hour, value) { + let { schedules, changes } = state; + for (const building of buildings) { + // Track update changes + updateChanges( + changes, + 'schedules', + building, + `${tab}_${day}_${hour}`, + schedules[building].SCHEDULES[tab][day][Number(hour)], + value + ); + + let daySchedule = schedules[building].SCHEDULES[tab][day]; + daySchedule[Number(hour)] = value; + schedules = { + ...schedules, + [building]: { + ...schedules[building], + SCHEDULES: { + ...schedules[building].SCHEDULES, + [tab]: { + ...schedules[building].SCHEDULES[tab], + [day]: daySchedule + } + } + } + }; + } + return { schedules }; +} + +function updateYearSchedule(state, buildings, month, value) { + let { schedules, changes } = state; + for (const building of buildings) { + // Track update changes + updateChanges( + changes, + 'schedules', + building, + `MONTHLY_MULTIPLIER_${months_short[month]}`, + schedules[building].MONTHLY_MULTIPLIER[month], + value + ); + + let monthSchedule = schedules[building].MONTHLY_MULTIPLIER; + monthSchedule[month] = value; + schedules = { + ...schedules, + [building]: { + ...schedules[building], + MONTHLY_MULTIPLIER: monthSchedule + } + }; + } + return { schedules }; +} + const inputData = (state = initialState, { type, payload }) => { switch (type) { case REQUEST_INPUTDATA: @@ -186,6 +247,28 @@ const inputData = (state = initialState, { type, payload }) => { payload.properties ) }; + case UPDATE_YEARSCHEDULE: + return { + ...state, + ...updateYearSchedule( + state, + payload.buildings, + payload.month, + payload.value + ) + }; + case UPDATE_DAYSCHEDULE: + return { + ...state, + ...updateDaySchedule( + state, + payload.buildings, + payload.tab, + payload.day, + payload.hour, + payload.value + ) + }; case DELETE_BUILDINGS: return { ...state, From a4fba31523ce1f46be9a7253f63021b2bea6f71a Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Fri, 22 Nov 2019 11:46:21 +0800 Subject: [PATCH 13/24] Add parameter to fetchBuildingSchedule to fetch all buildings --- src/renderer/actions/inputEditor.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/renderer/actions/inputEditor.js b/src/renderer/actions/inputEditor.js index 74fa1f1..69cc4b0 100644 --- a/src/renderer/actions/inputEditor.js +++ b/src/renderer/actions/inputEditor.js @@ -53,14 +53,20 @@ export const saveChanges = () => (dispatch, getState) => ); }); -export const fetchBuildingSchedule = buildings => (dispatch, getState) => { - const toFetch = buildings.filter( - building => !Object.keys(getState().inputData.schedules).includes(building) - ); +export const fetchBuildingSchedule = (buildings, fetchAll = false) => ( + dispatch, + getState +) => { + const toFetch = fetchAll + ? buildings + : buildings.filter( + building => + !Object.keys(getState().inputData.schedules).includes(building) + ); if (toFetch.length) { dispatch({ type: REQUEST_BUILDINGSCHEDULE }); let errors = {}; - const promises = buildings.map(building => + const promises = toFetch.map(building => axios .get(`http://localhost:5050/api/inputs/building-schedule/${building}`) .then(resp => { From f8638e71e9b705bdd631481937245ad358830d04 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Fri, 22 Nov 2019 11:47:03 +0800 Subject: [PATCH 14/24] Update save and discard changes to include schedules --- src/renderer/actions/inputEditor.js | 33 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/renderer/actions/inputEditor.js b/src/renderer/actions/inputEditor.js index 69cc4b0..94f6eee 100644 --- a/src/renderer/actions/inputEditor.js +++ b/src/renderer/actions/inputEditor.js @@ -40,13 +40,13 @@ export const fetchInputData = () => export const saveChanges = () => (dispatch, getState) => // eslint-disable-next-line no-undef new Promise((resolve, reject) => { - const { tables, geojsons, crs } = getState().inputData; + const { tables, geojsons, crs, schedules } = getState().inputData; dispatch( httpAction({ url: '/inputs/all-inputs', method: 'PUT', type: SAVE_INPUTDATA, - data: { tables, geojsons, crs }, + data: { tables, geojsons, crs, schedules }, onSuccess: data => resolve(data), onFailure: error => reject(error) }) @@ -97,18 +97,25 @@ export const fetchBuildingSchedule = (buildings, fetchAll = false) => ( return Promise.resolve(); }; -export const discardChanges = () => dispatch => +export const discardChanges = () => (dispatch, getState) => // eslint-disable-next-line no-undef - new Promise((resolve, reject) => { - dispatch( - httpAction({ - url: '/inputs/all-inputs', - type: DISCARD_INPUTDATA_CHANGES, - onSuccess: data => resolve(data), - onFailure: error => reject(error) - }) - ); - }); + Promise.all([ + // eslint-disable-next-line no-undef + new Promise((resolve, reject) => { + dispatch( + httpAction({ + url: '/inputs/all-inputs', + type: DISCARD_INPUTDATA_CHANGES, + onSuccess: data => resolve(data), + onFailure: error => reject(error) + }) + ); + }), + fetchBuildingSchedule(Object.keys(getState().inputData.schedules), true)( + dispatch, + getState + ) + ]); export const updateInputData = ( table = '', From a14b4ce16b079f4283195c211e003303c787e02f Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Fri, 22 Nov 2019 16:10:24 +0800 Subject: [PATCH 15/24] Fix hour header to start from 1 instead of 0 --- src/renderer/components/InputEditor/ScheduleEditor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index d164dfe..c87367a 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -145,7 +145,7 @@ const DataTable = ({ selected, tab, schedules }) => { columns: [ { title: 'DAY \\ HOUR', field: 'DAY', width: 100, headerSort: false }, ...[...Array(24).keys()].map(i => ({ - title: i.toString(), + title: (i + 1).toString(), field: i.toString(), headerSort: false, editor: 'input', From 1124d7cbedf6cb8b453689f3f58ba445af2dd508 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Fri, 22 Nov 2019 16:11:15 +0800 Subject: [PATCH 16/24] Fix table redrawing before data is fetched --- .../components/InputEditor/ScheduleEditor.js | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index c87367a..2fa52f8 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -101,13 +101,18 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { ) : (
- +
@@ -132,7 +137,7 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { ); }; -const DataTable = ({ selected, tab, schedules }) => { +const DataTable = ({ selected, tab, schedules, loading }) => { const tabulator = useRef(null); const divRef = useRef(null); const tooltipsRef = useRef({ selected, schedules, tab }); @@ -192,21 +197,20 @@ const DataTable = ({ selected, tab, schedules }) => { useEffect(() => { tooltipsRef.current = { selected, schedules, tab }; - tabulator.current && - tab && - selected.length && + tab && selected.every(building => Object.keys(schedules).includes(building)) && tabulator.current.updateOrAddData(parseData(schedules, selected, tab)); }, [selected, schedules, tab]); useEffect(() => { - tabulator.current.redraw(true); - }, [selected, tab]); + selected.every(building => Object.keys(schedules).includes(building)) && + tabulator.current.redraw(true); + }, [selected, tab, loading]); return
; }; -const YearTable = ({ selected, schedules }) => { +const YearTable = ({ selected, schedules, loading }) => { const tabulator = useRef(null); const divRef = useRef(null); const tooltipsRef = useRef({ selected, schedules }); @@ -273,12 +277,15 @@ const YearTable = ({ selected, schedules }) => { useEffect(() => { tooltipsRef.current = { selected, schedules }; - tabulator.current && - selected.length && - selected.every(building => Object.keys(schedules).includes(building)) && + selected.every(building => Object.keys(schedules).includes(building)) && tabulator.current.updateOrAddData(parseYearData(schedules, selected)); }, [schedules, selected]); + useEffect(() => { + selected.every(building => Object.keys(schedules).includes(building)) && + tabulator.current.redraw(true); + }, [selected, loading]); + return
; }; From 93c4b6bd5b83dfe8245bb0aee559daa74ee7672f Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Fri, 22 Nov 2019 16:13:19 +0800 Subject: [PATCH 17/24] Revert adding fetchBuildingSchedule fetch all parameter --- src/renderer/actions/inputEditor.js | 71 ++++++++++++----------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/src/renderer/actions/inputEditor.js b/src/renderer/actions/inputEditor.js index 94f6eee..8f31c61 100644 --- a/src/renderer/actions/inputEditor.js +++ b/src/renderer/actions/inputEditor.js @@ -53,48 +53,35 @@ export const saveChanges = () => (dispatch, getState) => ); }); -export const fetchBuildingSchedule = (buildings, fetchAll = false) => ( - dispatch, - getState -) => { - const toFetch = fetchAll - ? buildings - : buildings.filter( - building => - !Object.keys(getState().inputData.schedules).includes(building) - ); - if (toFetch.length) { - dispatch({ type: REQUEST_BUILDINGSCHEDULE }); - let errors = {}; - const promises = toFetch.map(building => - axios - .get(`http://localhost:5050/api/inputs/building-schedule/${building}`) - .then(resp => { - return { [building]: resp.data }; - }) - .catch(error => { - errors[building] = error.response.data; - }) - ); - // eslint-disable-next-line no-undef - return Promise.all(promises).then(values => { - if (Object.keys(errors).length) { - throw errors; - } else { - let out = {}; - for (const schedule of values) { - const building = Object.keys(schedule)[0]; - out[building] = schedule[building]; - } - dispatch({ - type: REQUEST_BUILDINGSCHEDULE_SUCCESS, - payload: out - }); - } - }); - } +export const fetchBuildingSchedule = buildings => dispatch => { + dispatch({ type: REQUEST_BUILDINGSCHEDULE }); + let errors = {}; + const promises = buildings.map(building => + axios + .get(`http://localhost:5050/api/inputs/building-schedule/${building}`) + .then(resp => { + return { [building]: resp.data }; + }) + .catch(error => { + errors[building] = error.response.data; + }) + ); // eslint-disable-next-line no-undef - return Promise.resolve(); + return Promise.all(promises).then(values => { + if (Object.keys(errors).length) { + throw errors; + } else { + let out = {}; + for (const schedule of values) { + const building = Object.keys(schedule)[0]; + out[building] = schedule[building]; + } + dispatch({ + type: REQUEST_BUILDINGSCHEDULE_SUCCESS, + payload: out + }); + } + }); }; export const discardChanges = () => (dispatch, getState) => @@ -111,7 +98,7 @@ export const discardChanges = () => (dispatch, getState) => }) ); }), - fetchBuildingSchedule(Object.keys(getState().inputData.schedules), true)( + fetchBuildingSchedule(Object.keys(getState().inputData.schedules))( dispatch, getState ) From a399914b0c31b55a321ad0bf61f0f6b0774a5b99 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Fri, 22 Nov 2019 16:14:15 +0800 Subject: [PATCH 18/24] Add debouncing to fetching building schedules --- .../components/InputEditor/ScheduleEditor.js | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index 2fa52f8..7951052 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -20,6 +20,7 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { const buildings = Object.keys(tables.zone || {}); const dispatch = useDispatch(); const divRef = useRef(null); + const timeoutRef = useRef(); const selectRow = (e, cell) => { const row = cell.getRow(); @@ -64,15 +65,23 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { tabulator.current && tabulator.current.deselectRow(); if (buildings.includes(selected[0])) { tabulator.current && tabulator.current.selectRow(selected); - setLoading(true); - dispatch(fetchBuildingSchedule(selected)) - .catch(error => { - console.log(error); - setErrors(error); - }) - .finally(() => { - setLoading(false); - }); + const missingSchedules = selected.filter( + building => !Object.keys(schedules).includes(building) + ); + if (missingSchedules.length) { + setLoading(true); + clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => { + dispatch(fetchBuildingSchedule(missingSchedules)) + .catch(error => { + console.log(error); + setErrors(error); + }) + .finally(() => { + setLoading(false); + }); + }, 1000); + } } tabulator.current && tabulator.current.getFilters().length && From c21102014985b03ef2a9fdbd2bc6c9e7f70656ff Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Fri, 22 Nov 2019 16:21:29 +0800 Subject: [PATCH 19/24] Fix change summary showing wrong hour --- src/renderer/components/InputEditor/ScheduleEditor.js | 2 +- src/renderer/reducers/inputEditor.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index 7951052..1b51052 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -181,7 +181,7 @@ const DataTable = ({ selected, tab, schedules, loading }) => { tooltipsRef.current.selected, tooltipsRef.current.tab, cell.getData().DAY, - cell.getField(), + Number(cell.getField()), cell.getValue() ) ); diff --git a/src/renderer/reducers/inputEditor.js b/src/renderer/reducers/inputEditor.js index 9590aa4..05cd0af 100644 --- a/src/renderer/reducers/inputEditor.js +++ b/src/renderer/reducers/inputEditor.js @@ -177,13 +177,13 @@ function updateDaySchedule(state, buildings, tab, day, hour, value) { changes, 'schedules', building, - `${tab}_${day}_${hour}`, - schedules[building].SCHEDULES[tab][day][Number(hour)], + `${tab}_${day}_${hour + 1}`, + schedules[building].SCHEDULES[tab][day][hour], value ); let daySchedule = schedules[building].SCHEDULES[tab][day]; - daySchedule[Number(hour)] = value; + daySchedule[hour] = value; schedules = { ...schedules, [building]: { From 50552bae447f37e276f51a295a3c2ecdb6784bd0 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Fri, 22 Nov 2019 16:50:17 +0800 Subject: [PATCH 20/24] Enable multi selection while holding ctrl --- .../components/InputEditor/ScheduleEditor.js | 26 ++++++++++++------- src/renderer/components/InputEditor/Table.js | 22 +++++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index 1b51052..2b14b90 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -24,16 +24,20 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { const selectRow = (e, cell) => { const row = cell.getRow(); - const selectedRows = cell - .getTable() - .getSelectedData() - .map(data => data.Name); - if (cell.getRow().isSelected()) { - dispatch( - setSelected(selectedRows.filter(name => name !== row.getIndex())) - ); + if (!e.ctrlKey) { + dispatch(setSelected([row.getIndex()])); } else { - dispatch(setSelected([...selectedRows, row.getIndex()])); + const selectedRows = cell + .getTable() + .getSelectedData() + .map(data => data.Name); + if (cell.getRow().isSelected()) { + dispatch( + setSelected(selectedRows.filter(name => name !== row.getIndex())) + ); + } else { + dispatch(setSelected([...selectedRows, row.getIndex()])); + } } }; @@ -70,7 +74,6 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { ); if (missingSchedules.length) { setLoading(true); - clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => { dispatch(fetchBuildingSchedule(missingSchedules)) .catch(error => { @@ -81,6 +84,9 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { setLoading(false); }); }, 1000); + } else { + clearTimeout(timeoutRef.current); + setLoading(false); } } tabulator.current && diff --git a/src/renderer/components/InputEditor/Table.js b/src/renderer/components/InputEditor/Table.js index cdc2a80..5fe6738 100644 --- a/src/renderer/components/InputEditor/Table.js +++ b/src/renderer/components/InputEditor/Table.js @@ -391,16 +391,20 @@ const useTableData = tab => { const selectRow = (e, cell) => { const row = cell.getRow(); - const selectedRows = cell - .getTable() - .getSelectedData() - .map(data => data.Name); - if (cell.getRow().isSelected()) { - dispatch( - setSelected(selectedRows.filter(name => name !== row.getIndex())) - ); + if (!e.ctrlKey) { + dispatch(setSelected([row.getIndex()])); } else { - dispatch(setSelected([...selectedRows, row.getIndex()])); + const selectedRows = cell + .getTable() + .getSelectedData() + .map(data => data.Name); + if (cell.getRow().isSelected()) { + dispatch( + setSelected(selectedRows.filter(name => name !== row.getIndex())) + ); + } else { + dispatch(setSelected([...selectedRows, row.getIndex()])); + } } }; From b6689e1bf4db540e5462a59eff49bbb3e4b9ffe8 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Mon, 25 Nov 2019 13:49:12 +0800 Subject: [PATCH 21/24] Add colors to table cells based on its values --- package.json | 1 + .../components/InputEditor/ScheduleEditor.js | 23 +++++++- yarn.lock | 56 ++++++++++++++++++- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0fdb08b..b609806 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "antd": "^3.21.3", "axios": "^0.19.0", "babel-plugin-import": "^1.12.0", + "color-interpolate": "^1.0.5", "connected-react-router": "^6.5.2", "deck.gl": "^7.2.2", "electron-updater": "^4.1.2", diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index 2b14b90..d247827 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -1,5 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; +import interpolate from 'color-interpolate'; import { fetchBuildingSchedule, setSelected, @@ -12,6 +13,8 @@ import './ScheduleEditor.css'; import { months_short } from '../../constants/months'; import { Tabs, Spin } from 'antd'; +const colormap = interpolate(['white', '#006ad5']); + const ScheduleEditor = ({ selected, schedules, tabulator }) => { const { tables } = useSelector(state => state.inputData); const [tab, setTab] = useState(null); @@ -322,15 +325,33 @@ const ScheduleTab = ({ tab, setTab, schedules }) => { }; const formatCellStyle = cell => { - if (cell.getValue() == 'DIFF') { + const states = ['OFF', 'SETBACK', 'SETPOINT']; + const value = cell.getValue(); + if (value == 'DIFF') { cell.getElement().style.fontWeight = 'bold'; cell.getElement().style.fontStyle = 'italic'; } else { + if (!isNaN(value)) { + cell.getElement().style.backgroundColor = addRGBAlpha( + colormap(value), + 0.5 + ); + } else if (states.includes(value)) { + cell.getElement().style.backgroundColor = addRGBAlpha( + colormap(states.indexOf(value) / (states.length - 1)), + 0.5 + ); + } cell.getElement().style.fontWeight = 'normal'; cell.getElement().style.fontStyle = 'normal'; } }; +const addRGBAlpha = (color, opacity) => { + const rgb = color.replace(/[^\d,]/g, '').split(','); + return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opacity}`; +}; + const parseYearData = (schedules, selected) => { let out = []; for (const building of selected) { diff --git a/yarn.lock b/yarn.lock index 62564bd..c0c3f10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2695,6 +2695,11 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.1, ajv@^6.10.2, ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +almost-equal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/almost-equal/-/almost-equal-1.1.0.tgz#f851c631138757994276aa2efbe8dfa3066cccdd" + integrity sha1-+FHGMROHV5lCdqou++jfowZszN0= + alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -3623,6 +3628,11 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +clamp@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634" + integrity sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ= + clap@^1.0.9: version "1.2.3" resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" @@ -3740,6 +3750,16 @@ color-convert@^1.3.0, color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-interpolate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/color-interpolate/-/color-interpolate-1.0.5.tgz#d5710ce4244bd8b9feeda003f409edd4073b6217" + integrity sha512-EcWwYtBJdbeHyYq/y5QwVWLBUm4s7+8K37ycgO9OdY6YuAEa0ywAY+ItlAxE1UO5bXW4ugxNhStTV3rsTZ35ZA== + dependencies: + clamp "^1.0.1" + color-parse "^1.2.0" + color-space "^1.14.3" + lerp "^1.0.3" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" @@ -3750,6 +3770,23 @@ color-name@^1.0.0: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-parse@^1.2.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/color-parse/-/color-parse-1.3.8.tgz#eaf54cd385cb34c0681f18c218aca38478082fa3" + integrity sha512-1Y79qFv0n1xair3lNMTNeoFvmc3nirMVBij24zbs1f13+7fPpQClMg5b4AuKXLt3szj7BRlHMCXHplkce6XlmA== + dependencies: + color-name "^1.0.0" + defined "^1.0.0" + is-plain-obj "^1.1.0" + +color-space@^1.14.3: + version "1.16.0" + resolved "https://registry.yarnpkg.com/color-space/-/color-space-1.16.0.tgz#611781bca41cd8582a1466fd9e28a7d3d89772a2" + integrity sha512-A6WMiFzunQ8KEPFmj02OnnoUnqhmSaHaZ/0LVFcPTdlvm8+3aMJ5x1HRHy3bDHPkovkf4sS0f4wsVvwk71fKkg== + dependencies: + hsluv "^0.0.3" + mumath "^3.3.4" + color-string@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" @@ -6153,6 +6190,11 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" +hsluv@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/hsluv/-/hsluv-0.0.3.tgz#829107dafb4a9f8b52a1809ed02e091eade6754c" + integrity sha1-gpEH2vtKn4tSoYCe0C4JHq3mdUw= + html-comment-regex@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" @@ -6734,7 +6776,7 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" -is-plain-obj@^1.0.0: +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= @@ -7049,6 +7091,11 @@ lcid@^2.0.0: dependencies: invert-kv "^2.0.0" +lerp@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/lerp/-/lerp-1.0.3.tgz#a18c8968f917896de15ccfcc28d55a6b731e776e" + integrity sha1-oYyJaPkXiW3hXM/MKNVaa3Med24= + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -7653,6 +7700,13 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" +mumath@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/mumath/-/mumath-3.3.4.tgz#48d4a0f0fd8cad4e7b32096ee89b161a63d30bbf" + integrity sha1-SNSg8P2MrU57Mglu6JsWGmPTC78= + dependencies: + almost-equal "^1.1.0" + murmurhash-js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" From f682eb23a586614a27d9ce0bbf514d4114f0704f Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Mon, 25 Nov 2019 14:31:23 +0800 Subject: [PATCH 22/24] Add tooltip for multi selection --- src/renderer/components/InputEditor/Table.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/InputEditor/Table.js b/src/renderer/components/InputEditor/Table.js index 5fe6738..f7100bb 100644 --- a/src/renderer/components/InputEditor/Table.js +++ b/src/renderer/components/InputEditor/Table.js @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import { useSelector, useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; -import { Card, Button, Modal, message, Tooltip } from 'antd'; +import { Card, Button, Modal, message, Tooltip, Icon } from 'antd'; import { setSelected, updateInputData, @@ -27,6 +27,14 @@ const Table = ({ tab }) => { + + + } extra={ } From c791f84ecdb70177974b9afc3d1696ca57ad8d18 Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Mon, 25 Nov 2019 15:20:54 +0800 Subject: [PATCH 23/24] Add GFA to tooltip for zone buildings --- src/renderer/components/Map/Map.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/Map/Map.js b/src/renderer/components/Map/Map.js index 61bddac..6277d94 100644 --- a/src/renderer/components/Map/Map.js +++ b/src/renderer/components/Map/Map.js @@ -409,16 +409,19 @@ function updateTooltip({ x, y, object, layer }) { let innerHTML = ''; if (layer.id === 'zone' || layer.id === 'surroundings') { - Object.keys(properties).forEach(key => { - innerHTML += `
${key}: ${properties[key]}
`; - }); - let area = calcArea(object); - innerHTML += - `
area: ${Math.round(area * 1000) / - 1000}m2
` + - `
volume: ${Math.round( - area * properties['height_ag'] * 1000 - ) / 1000}m3
`; + innerHTML += `
Name: ${properties.Name}

`; + Object.keys(properties) + .sort() + .forEach(key => { + if (key != 'Name') + innerHTML += `
${key}: ${properties[key]}
`; + }); + let area = Math.round(calcArea(object) * 1000) / 1000; + innerHTML += `
Floor Area: ${area}m2
`; + if (layer.id === 'zone') + innerHTML += `
GFA: ${Math.round( + (properties['floors_ag'] + properties['floors_bg']) * area * 1000 + ) / 1000}m2
`; } else if (layer.id === 'dc' || layer.id === 'dh') { Object.keys(properties).forEach(key => { if (key !== 'Building' && properties[key] === 'NONE') return null; From e466bc874a297e92365311745160a81082f3403b Mon Sep 17 00:00:00 2001 From: Reynold Mok Date: Mon, 25 Nov 2019 15:33:04 +0800 Subject: [PATCH 24/24] Fix debounce logic for fetching schedules --- src/renderer/components/InputEditor/ScheduleEditor.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/InputEditor/ScheduleEditor.js b/src/renderer/components/InputEditor/ScheduleEditor.js index d247827..fb779ce 100644 --- a/src/renderer/components/InputEditor/ScheduleEditor.js +++ b/src/renderer/components/InputEditor/ScheduleEditor.js @@ -72,6 +72,8 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { tabulator.current && tabulator.current.deselectRow(); if (buildings.includes(selected[0])) { tabulator.current && tabulator.current.selectRow(selected); + setLoading(false); + clearTimeout(timeoutRef.current); const missingSchedules = selected.filter( building => !Object.keys(schedules).includes(building) ); @@ -87,9 +89,6 @@ const ScheduleEditor = ({ selected, schedules, tabulator }) => { setLoading(false); }); }, 1000); - } else { - clearTimeout(timeoutRef.current); - setLoading(false); } } tabulator.current &&