Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keyboard shortcuts to sources panel #68

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 13 additions & 139 deletions packages/base/src/panelview/components/layers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import {
IJGISLayerGroup,
IJGISLayerTree,
IJupyterGISClientState,
IJupyterGISModel,
ISelection,
SelectionType
IJupyterGISModel
} from '@jupytergis/schema';
import { DOMUtils } from '@jupyterlab/apputils';
import {
Expand All @@ -13,7 +11,6 @@ import {
ReactWidget,
caretDownIcon
} from '@jupyterlab/ui-components';
import { Message } from '@lumino/messaging';
import { Panel } from '@lumino/widgets';
import React, {
MouseEvent as ReactMouseEvent,
Expand All @@ -23,6 +20,7 @@ import React, {
import { icons } from '../../constants';
import { nonVisibilityIcon, visibilityIcon } from '../../icons';
import { IControlPanelModel } from '../../types';
import { ILeftPanelClickHandlerParams, ILeftPanelOptions } from '../leftpanel';

const LAYERS_PANEL_CLASS = 'jp-gis-layerPanel';
const LAYER_GROUP_CLASS = 'jp-gis-layerGroup';
Expand All @@ -34,33 +32,15 @@ const LAYER_TITLE_CLASS = 'jp-gis-layerTitle';
const LAYER_ICON_CLASS = 'jp-gis-layerIcon';
const LAYER_TEXT_CLASS = 'jp-gis-layerText';

/**
* The namespace for the layers panel.
*/
export namespace LayersPanel {
/**
* Options of the layers panel widget.
*/
export interface IOptions {
model: IControlPanelModel;
}

export interface IClickHandlerParams {
type: SelectionType;
item: string;
nodeId?: string;
event: ReactMouseEvent;
}
}

/**
* The layers panel widget.
*/
export class LayersPanel extends Panel {
constructor(options: LayersPanel.IOptions) {
constructor(options: ILeftPanelOptions) {
super();
this._model = options.model;
this._lastSelectedNodeId = '';
this._onSelect = options.onSelect;

this.id = 'jupytergis::layerTree';
this.addClass(LAYERS_PANEL_CLASS);

Expand All @@ -74,126 +54,20 @@ export class LayersPanel extends Panel {
);
}

protected onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
const node = this.node;
node.addEventListener('mouseup', this);
}

protected onBeforeDetach(msg: Message): void {
super.onBeforeDetach(msg);
const node = this.node;
node.removeEventListener('mouseup', this);
}

handleEvent(event: Event): void {
switch (event.type) {
case 'mouseup':
this._mouseUpEvent(event as MouseEvent);
break;
default:
break;
}
}

private _mouseUpEvent(event: MouseEvent): void {
// If we click on empty space in the layer panel, keep the focus on the last selected element
const node = document.getElementById(this._lastSelectedNodeId);
if (!node) {
return;
}

node.focus();
}

/**
* Function to call when a layer is selected from a component of the panel.
*
* @param item - the selected layer or group.
*/
private _onSelect = ({
private _model: IControlPanelModel | undefined;
private _onSelect: ({
type,
item,
nodeId,
event
}: LayersPanel.IClickHandlerParams) => {
if (!this._model || !nodeId) {
return;
}

const { jGISModel } = this._model;
const selectedValue = jGISModel?.localState?.selected?.value;
const node = document.getElementById(nodeId);

if (!node) {
return;
}

node.tabIndex = 0;
node.focus();

// Early return if no selection exists
if (!selectedValue) {
this.resetSelected(type, nodeId, item);
return;
}

// Don't want to reset selected if right clicking a selected item
if (!event.ctrlKey && event.button === 2 && item in selectedValue) {
return;
}

// Reset selection for normal left click
if (!event.ctrlKey) {
this.resetSelected(type, nodeId, item);
return;
}

if (nodeId) {
// Check if new selection is the same type as previous selections
const isSelectedSameType = Object.values(selectedValue).some(
selection => selection.type === type
);

if (!isSelectedSameType) {
// Selecting a new type, so reset selected
this.resetSelected(type, nodeId, item);
return;
}

// If types are the same add the selection
const updatedSelectedValue = {
...selectedValue,
[item]: { type, selectedNodeId: nodeId }
};
this._lastSelectedNodeId = nodeId;

jGISModel.syncSelected(updatedSelectedValue, this.id);
}
};

resetSelected(type: SelectionType, nodeId?: string, item?: string) {
const selection: { [key: string]: ISelection } = {};
if (item && nodeId) {
selection[item] = {
type,
selectedNodeId: nodeId
};
this._lastSelectedNodeId = nodeId;
}
this._model?.jGISModel?.syncSelected(selection, this.id);
}

private _model: IControlPanelModel | undefined;
private _lastSelectedNodeId: string;
nodeId
}: ILeftPanelClickHandlerParams) => void;
}

/**
* Properties of the layers body component.
*/
interface IBodyProps {
model: IControlPanelModel;
onSelect: ({ type, item, nodeId }: LayersPanel.IClickHandlerParams) => void;
onSelect: ({ type, item, nodeId }: ILeftPanelClickHandlerParams) => void;
}

/**
Expand All @@ -215,7 +89,7 @@ function LayersBodyComponent(props: IBodyProps): JSX.Element {
item,
nodeId,
event
}: LayersPanel.IClickHandlerParams) => {
}: ILeftPanelClickHandlerParams) => {
props.onSelect({ type, item, nodeId, event });
};

Expand Down Expand Up @@ -270,7 +144,7 @@ function LayersBodyComponent(props: IBodyProps): JSX.Element {
interface ILayerGroupProps {
gisModel: IJupyterGISModel | undefined;
group: IJGISLayerGroup | undefined;
onClick: ({ type, item, nodeId }: LayersPanel.IClickHandlerParams) => void;
onClick: ({ type, item, nodeId }: ILeftPanelClickHandlerParams) => void;
}

/**
Expand Down Expand Up @@ -363,7 +237,7 @@ function LayerGroupComponent(props: ILayerGroupProps): JSX.Element {
interface ILayerProps {
gisModel: IJupyterGISModel | undefined;
layerId: string;
onClick: ({ type, item, nodeId }: LayersPanel.IClickHandlerParams) => void;
onClick: ({ type, item, nodeId }: ILeftPanelClickHandlerParams) => void;
}

function isSelected(layerId: string, model: IJupyterGISModel | undefined) {
Expand Down
108 changes: 12 additions & 96 deletions packages/base/src/panelview/components/sources.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import {
IJupyterGISClientState,
IJupyterGISModel,
ISelection,
SelectionType
} from '@jupytergis/schema';
import { IJupyterGISClientState, IJupyterGISModel } from '@jupytergis/schema';
import { DOMUtils } from '@jupyterlab/apputils';
import { LabIcon, ReactWidget } from '@jupyterlab/ui-components';
import { Panel } from '@lumino/widgets';
import React, { MouseEvent, useEffect, useState } from 'react';
import { icons } from '../../constants';
import { IControlPanelModel } from '../../types';
import { ILeftPanelClickHandlerParams, ILeftPanelOptions } from '../leftpanel';

const SOURCES_PANEL_CLASS = 'jp-gis-sourcePanel';
const SOURCE_CLASS = 'jp-gis-source';
Expand All @@ -19,32 +15,15 @@ const SOURCE_TEXT_CLASS = 'jp-gis-sourceText';
const SOURCE_UNUSED = 'jp-gis-sourceUnused';
const SOURCE_INFO = 'jp-gis-sourceInfo';

/**
* The namespace for the sources panel.
*/
export namespace SourcesPanel {
/**
* Options of the sources panel widget.
*/
export interface IOptions {
model: IControlPanelModel;
}

export interface IClickHandlerParams {
type: SelectionType;
item: string;
nodeId?: string;
event: MouseEvent;
}
}

/**
* The sources panel widget.
*/
export class SourcesPanel extends Panel {
constructor(options: SourcesPanel.IOptions) {
constructor(options: ILeftPanelOptions) {
super();
this._model = options.model;
this._onSelect = options.onSelect;

this.id = 'jupytergis::sourcesPanel';
this.addClass(SOURCES_PANEL_CLASS);

Expand All @@ -58,83 +37,20 @@ export class SourcesPanel extends Panel {
);
}

/**
* Function to call when a source is selected from a component of the panel.
*
* @param item - the selected source.
*/
private _onSelect = ({
private _model: IControlPanelModel | undefined;
private _onSelect: ({
type,
item,
nodeId,
event
}: SourcesPanel.IClickHandlerParams) => {
if (!this._model) {
return;
}

const { jGISModel } = this._model;
const selectedValue = jGISModel?.localState?.selected?.value;

// Early return if no selection exists
if (!selectedValue) {
this.resetSelected(type, nodeId, item);
return;
}

// Don't want to reset selected if right clicking a selected item
if (!event.ctrlKey && event.button === 2 && item in selectedValue) {
return;
}

// Reset selection for normal left click
if (!event.ctrlKey) {
this.resetSelected(type, nodeId, item);
return;
}

if (nodeId) {
// Check if new selection is the same type as previous selections
const isSelectedSameType = Object.values(selectedValue).some(
selection => selection.type === type
);

if (!isSelectedSameType) {
// Selecting a new type, so reset selected
this.resetSelected(type, nodeId, item);
return;
}

// If types are the same add the selection
const updatedSelectedValue = {
...selectedValue,
[item]: { type, selectedNodeId: nodeId }
};

jGISModel.syncSelected(updatedSelectedValue, this.id);
}
};

resetSelected(type: SelectionType, nodeId?: string, item?: string) {
const selection: { [key: string]: ISelection } = {};
if (item && nodeId) {
selection[item] = {
type,
selectedNodeId: nodeId
};
}
this._model?.jGISModel?.syncSelected(selection, this.id);
}

private _model: IControlPanelModel | undefined;
nodeId
}: ILeftPanelClickHandlerParams) => void;
}

/**
* Properties of the sources body component.
*/
interface IBodyProps {
model: IControlPanelModel;
onSelect: ({ type, item, nodeId }: SourcesPanel.IClickHandlerParams) => void;
onSelect: ({ type, item, nodeId }: ILeftPanelClickHandlerParams) => void;
}

/**
Expand All @@ -156,7 +72,7 @@ function SourcesBodyComponent(props: IBodyProps): JSX.Element {
item,
nodeId,
event
}: SourcesPanel.IClickHandlerParams) => {
}: ILeftPanelClickHandlerParams) => {
props.onSelect({ type, item, nodeId, event });
};

Expand Down Expand Up @@ -206,7 +122,7 @@ function SourcesBodyComponent(props: IBodyProps): JSX.Element {
interface ISourceProps {
gisModel: IJupyterGISModel | undefined;
sourceId: string;
onClick: ({ type, item, nodeId }: SourcesPanel.IClickHandlerParams) => void;
onClick: ({ type, item, nodeId }: ILeftPanelClickHandlerParams) => void;
}

function isSelected(sourceId: string, model: IJupyterGISModel | undefined) {
Expand Down
Loading
Loading