Loading src/nova/trame/model/data_selector.py +2 −0 Original line number Diff line number Diff line Loading @@ -159,6 +159,8 @@ class DataSelectorModel: if not self.state.user_directory: return None # We don't want to pull all user directory content as there can be an extremely large amount of content. # To deal with this, we make an assumption that anything the user wants exposed is placed in the nova directory. return Path("/SNS/users") / self.state.user_directory / "nova" def get_directories(self) -> List[str]: Loading src/nova/trame/view/components/data_selector.py +14 −8 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ class DataSelector(datagrid.VGrid): If unset, the `all` strategy will be used. show_user_directories : bool, optional Whether or not to allow users to select data files from user directories. Ignored if the facility parameter is set. is set. Please note that the component only looks for a "nova" directory and ignores all other content. **kwargs All other arguments will be passed to the underlying `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`_. Loading Loading @@ -89,7 +89,7 @@ class DataSelector(datagrid.VGrid): self._flush_state = f"flushState('{self._v_model_name_in_state}');" self._reset_rv_grid = client.JSEval( exec=f"window.rvUpdateCheckboxes('{self._v_model}', '{self._datafiles_name}')" exec=f"window.grid_manager.get('{self._revogrid_id}').updateCheckboxes()" ).exec self._reset_state = client.JSEval(exec=f"{self._v_model} = []; {self._flush_state}").exec Loading Loading @@ -144,13 +144,12 @@ class DataSelector(datagrid.VGrid): can_focus=False, columns=( "[{" " cellTemplate: (createElement, props) => window.rvCellTemplate(createElement, props)," " columnTemplate: (createElement, props) => window.rvColumnTemplate(createElement, props)," f" datafiles_key: '{self._datafiles_name}'," f" model_key: '{self._v_model}'," " cellTemplate: (createElement, props) =>" f" window.grid_manager.get('{self._revogrid_id}').cellTemplate(createElement, props)," " columnTemplate: (createElement) =>" f" window.grid_manager.get('{self._revogrid_id}').columnTemplate(createElement)," " name: 'Available Datafiles'," " prop: 'title'," f" state_key: '{self._v_model_name_in_state}'," "}]", ), frame_size=10, Loading @@ -170,7 +169,14 @@ class DataSelector(datagrid.VGrid): # Sets up some JavaScript event handlers when the component is mounted. with self: client.ClientTriggers( mounted=f"window.rvOnMount('{self._revogrid_id}', '{self._v_model}', '{self._datafiles_name}');" mounted=( "window.grid_manager.add(" f" '{self._revogrid_id}'," f" '{self._v_model}'," f" '{self._datafiles_name}'," f" '{self._v_model_name_in_state}'" ")" ) ) with cast( Loading src/nova/trame/view/theme/assets/js/revo_grid.js +97 −72 Original line number Diff line number Diff line window.rvOnMount = function(id, modelKey, dataKey) { const grid = document.querySelector(`#${id}`) grid.addEventListener('viewportscroll', () => { window.rvUpdateCheckboxes(modelKey, dataKey) class RevoGrid { constructor(id, modelKey, dataKey, stateKey) { this.id = id this.modelKey = modelKey this.dataKey = dataKey this.stateKey = stateKey this.grid = document.querySelector(`#${this.id}`) this.grid.addEventListener('viewportscroll', () => { this.updateCheckboxes() }) } window.rvUpdateCheckboxes = function(modelKey, dataKey) { updateCheckboxes() { const trameState = window.trame.state.state const modelValue = _.get(trameState, modelKey) const availableData = _.get(trameState, dataKey) const selectAllCheckbox = document.querySelector(".header-content input") const rowCheckboxes = document.querySelectorAll(".rgCell") const modelValue = _.get(trameState, this.modelKey) const availableData = _.get(trameState, this.dataKey) const selectAllCheckbox = this.grid.querySelector(".header-content input") const rowCheckboxes = this.grid.querySelectorAll(".rgCell") if (selectAllCheckbox === null) { return Loading @@ -28,55 +34,74 @@ window.rvUpdateCheckboxes = function(modelKey, dataKey) { } rowCheckboxes.forEach((element) => { input = element.querySelector('input') rowIndex = element.dataset.rgrow const input = element.querySelector('input') const rowIndex = element.dataset.rgrow input.checked = modelValue.includes(availableData[rowIndex].path) }) } window.rvCellTemplate = function(createElement, props) { cellTemplate(createElement, props) { const inputVNode = createElement('input', { type: 'checkbox', onChange: (e) => { const trameState = window.trame.state.state const modelValue = _.get(trameState, props.column.model_key) const modelValue = _.get(trameState, this.modelKey) const path = props.data[props.rowIndex].path const index = modelValue.indexOf(path) // We need to assign instead of modifying in place in order for the Trame watcher to pick up changes. if (e.target.checked && index < 0) { _.set(trameState, props.column.model_key, _.concat(modelValue, path)) _.set(trameState, this.modelKey, _.concat(modelValue, path)) } else if (index >= 0) { _.set(trameState, props.column.model_key, modelValue.toSpliced(index, 1)) _.set(trameState, this.modelKey, modelValue.toSpliced(index, 1)) } // Update the UI window.rvUpdateCheckboxes(props.column.model_key, props.column.datafiles_key) window.trame.state.dirty(props.column.state_key) this.updateCheckboxes(this.modelKey, this.dataKey) window.trame.state.dirty(this.stateKey) }, }) return createElement('label', undefined, inputVNode, props.model[props.prop]) } window.rvColumnTemplate = function (createElement, props) { columnTemplate(createElement) { const inputVNode = createElement('input', { type: 'checkbox', onChange: (e) => { const trameState = window.trame.state.state const availableData = _.get(trameState, props.datafiles_key) const availableData = _.get(trameState, this.dataKey) if (e.target.checked) { _.set(trameState, props.model_key, availableData.map((item) => item.path)) _.set(trameState, this.modelKey, availableData.map((item) => item.path)) } else { _.set(trameState, props.model_key, []) _.set(trameState, this.modelKey, []) } // Update the UI window.rvUpdateCheckboxes(props.model_key, props.datafiles_key) window.trame.state.dirty(props.state_key) this.updateCheckboxes(this.modelKey, this.dataKey) window.trame.state.dirty(this.stateKey) }, }) console.log(inputVNode) return [inputVNode, 'Available Datafiles'] } } class RevoGridManager { constructor() { this.grids = {} } add(id, modelKey, dataKey, stateKey) { this.grids[id] = new RevoGrid(id, modelKey, dataKey, stateKey) } get(id) { return this.grids[id] } } window.grid_manager = new RevoGridManager() Loading
src/nova/trame/model/data_selector.py +2 −0 Original line number Diff line number Diff line Loading @@ -159,6 +159,8 @@ class DataSelectorModel: if not self.state.user_directory: return None # We don't want to pull all user directory content as there can be an extremely large amount of content. # To deal with this, we make an assumption that anything the user wants exposed is placed in the nova directory. return Path("/SNS/users") / self.state.user_directory / "nova" def get_directories(self) -> List[str]: Loading
src/nova/trame/view/components/data_selector.py +14 −8 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ class DataSelector(datagrid.VGrid): If unset, the `all` strategy will be used. show_user_directories : bool, optional Whether or not to allow users to select data files from user directories. Ignored if the facility parameter is set. is set. Please note that the component only looks for a "nova" directory and ignores all other content. **kwargs All other arguments will be passed to the underlying `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`_. Loading Loading @@ -89,7 +89,7 @@ class DataSelector(datagrid.VGrid): self._flush_state = f"flushState('{self._v_model_name_in_state}');" self._reset_rv_grid = client.JSEval( exec=f"window.rvUpdateCheckboxes('{self._v_model}', '{self._datafiles_name}')" exec=f"window.grid_manager.get('{self._revogrid_id}').updateCheckboxes()" ).exec self._reset_state = client.JSEval(exec=f"{self._v_model} = []; {self._flush_state}").exec Loading Loading @@ -144,13 +144,12 @@ class DataSelector(datagrid.VGrid): can_focus=False, columns=( "[{" " cellTemplate: (createElement, props) => window.rvCellTemplate(createElement, props)," " columnTemplate: (createElement, props) => window.rvColumnTemplate(createElement, props)," f" datafiles_key: '{self._datafiles_name}'," f" model_key: '{self._v_model}'," " cellTemplate: (createElement, props) =>" f" window.grid_manager.get('{self._revogrid_id}').cellTemplate(createElement, props)," " columnTemplate: (createElement) =>" f" window.grid_manager.get('{self._revogrid_id}').columnTemplate(createElement)," " name: 'Available Datafiles'," " prop: 'title'," f" state_key: '{self._v_model_name_in_state}'," "}]", ), frame_size=10, Loading @@ -170,7 +169,14 @@ class DataSelector(datagrid.VGrid): # Sets up some JavaScript event handlers when the component is mounted. with self: client.ClientTriggers( mounted=f"window.rvOnMount('{self._revogrid_id}', '{self._v_model}', '{self._datafiles_name}');" mounted=( "window.grid_manager.add(" f" '{self._revogrid_id}'," f" '{self._v_model}'," f" '{self._datafiles_name}'," f" '{self._v_model_name_in_state}'" ")" ) ) with cast( Loading
src/nova/trame/view/theme/assets/js/revo_grid.js +97 −72 Original line number Diff line number Diff line window.rvOnMount = function(id, modelKey, dataKey) { const grid = document.querySelector(`#${id}`) grid.addEventListener('viewportscroll', () => { window.rvUpdateCheckboxes(modelKey, dataKey) class RevoGrid { constructor(id, modelKey, dataKey, stateKey) { this.id = id this.modelKey = modelKey this.dataKey = dataKey this.stateKey = stateKey this.grid = document.querySelector(`#${this.id}`) this.grid.addEventListener('viewportscroll', () => { this.updateCheckboxes() }) } window.rvUpdateCheckboxes = function(modelKey, dataKey) { updateCheckboxes() { const trameState = window.trame.state.state const modelValue = _.get(trameState, modelKey) const availableData = _.get(trameState, dataKey) const selectAllCheckbox = document.querySelector(".header-content input") const rowCheckboxes = document.querySelectorAll(".rgCell") const modelValue = _.get(trameState, this.modelKey) const availableData = _.get(trameState, this.dataKey) const selectAllCheckbox = this.grid.querySelector(".header-content input") const rowCheckboxes = this.grid.querySelectorAll(".rgCell") if (selectAllCheckbox === null) { return Loading @@ -28,55 +34,74 @@ window.rvUpdateCheckboxes = function(modelKey, dataKey) { } rowCheckboxes.forEach((element) => { input = element.querySelector('input') rowIndex = element.dataset.rgrow const input = element.querySelector('input') const rowIndex = element.dataset.rgrow input.checked = modelValue.includes(availableData[rowIndex].path) }) } window.rvCellTemplate = function(createElement, props) { cellTemplate(createElement, props) { const inputVNode = createElement('input', { type: 'checkbox', onChange: (e) => { const trameState = window.trame.state.state const modelValue = _.get(trameState, props.column.model_key) const modelValue = _.get(trameState, this.modelKey) const path = props.data[props.rowIndex].path const index = modelValue.indexOf(path) // We need to assign instead of modifying in place in order for the Trame watcher to pick up changes. if (e.target.checked && index < 0) { _.set(trameState, props.column.model_key, _.concat(modelValue, path)) _.set(trameState, this.modelKey, _.concat(modelValue, path)) } else if (index >= 0) { _.set(trameState, props.column.model_key, modelValue.toSpliced(index, 1)) _.set(trameState, this.modelKey, modelValue.toSpliced(index, 1)) } // Update the UI window.rvUpdateCheckboxes(props.column.model_key, props.column.datafiles_key) window.trame.state.dirty(props.column.state_key) this.updateCheckboxes(this.modelKey, this.dataKey) window.trame.state.dirty(this.stateKey) }, }) return createElement('label', undefined, inputVNode, props.model[props.prop]) } window.rvColumnTemplate = function (createElement, props) { columnTemplate(createElement) { const inputVNode = createElement('input', { type: 'checkbox', onChange: (e) => { const trameState = window.trame.state.state const availableData = _.get(trameState, props.datafiles_key) const availableData = _.get(trameState, this.dataKey) if (e.target.checked) { _.set(trameState, props.model_key, availableData.map((item) => item.path)) _.set(trameState, this.modelKey, availableData.map((item) => item.path)) } else { _.set(trameState, props.model_key, []) _.set(trameState, this.modelKey, []) } // Update the UI window.rvUpdateCheckboxes(props.model_key, props.datafiles_key) window.trame.state.dirty(props.state_key) this.updateCheckboxes(this.modelKey, this.dataKey) window.trame.state.dirty(this.stateKey) }, }) console.log(inputVNode) return [inputVNode, 'Available Datafiles'] } } class RevoGridManager { constructor() { this.grids = {} } add(id, modelKey, dataKey, stateKey) { this.grids[id] = new RevoGrid(id, modelKey, dataKey, stateKey) } get(id) { return this.grids[id] } } window.grid_manager = new RevoGridManager()