mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
add drag and drop capabilities for the order of cluster icons on the side bar
Signed-off-by: Sebastian Malton <smalton@mirantis.com>
This commit is contained in:
1 parent
435baaea00
commit
b0acf0a472
5 files changed
+172
-25
No files matched your search
@@ -18,9 +18,18 @@ export interface ClusterIconUpload {
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface ClusterIconOrdering {
|
||||
[workspaceId: string]: ClusterId[]
|
||||
}
|
||||
|
||||
interface ClusterIconOrderingMap {
|
||||
[id: string]: number
|
||||
}
|
||||
|
||||
export interface ClusterStoreModel {
|
||||
activeCluster?: ClusterId; // last opened cluster
|
||||
clusters?: ClusterModel[]
|
||||
iconOrder?: ClusterIconOrdering,
|
||||
}
|
||||
|
||||
export type ClusterId = string;
|
||||
@@ -83,6 +92,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
@observable activeClusterId: ClusterId;
|
||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusterOrders: ClusterIconOrdering = {};
|
||||
|
||||
@computed get activeCluster(): Cluster | null {
|
||||
return this.getById(this.activeClusterId);
|
||||
@@ -113,8 +123,26 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
return this.clusters.get(id);
|
||||
}
|
||||
|
||||
getByWorkspaceId(workspaceId: string): Cluster[] {
|
||||
return this.clustersList.filter(cluster => cluster.workspace === workspaceId)
|
||||
getByWorkspaceId(workspaceId: string, sorted = true): Cluster[] {
|
||||
const ordering = this.clusterOrders[workspaceId]?.reduce((acc: ClusterIconOrderingMap, cur, i) => { acc[cur] = i; return acc; }, {});
|
||||
const clusters = this.clustersList.filter(cluster => cluster.workspace === workspaceId);
|
||||
if (!ordering || !sorted) {
|
||||
return clusters;
|
||||
}
|
||||
|
||||
const sortedClusters = [];
|
||||
const unsortedClusters = [];
|
||||
|
||||
for (const cluster of clusters) {
|
||||
const index = ordering[cluster.id];
|
||||
if (typeof index === "number" && index >= 0) {
|
||||
sortedClusters[index] = cluster;
|
||||
} else {
|
||||
unsortedClusters.push(cluster);
|
||||
}
|
||||
}
|
||||
|
||||
return [...sortedClusters.filter(c => c), ...unsortedClusters];
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -144,17 +172,19 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
@action
|
||||
removeByWorkspaceId(workspaceId: string) {
|
||||
this.getByWorkspaceId(workspaceId).forEach(cluster => {
|
||||
this.getByWorkspaceId(workspaceId, false).forEach(cluster => {
|
||||
this.removeById(cluster.id)
|
||||
})
|
||||
}
|
||||
|
||||
@action
|
||||
protected fromStore({ activeCluster, clusters = [] }: ClusterStoreModel = {}) {
|
||||
protected fromStore({ activeCluster, clusters = [], iconOrder = {} }: ClusterStoreModel = {}) {
|
||||
const currentClusters = this.clusters.toJS();
|
||||
const newClusters = new Map<ClusterId, Cluster>();
|
||||
const removedClusters = new Map<ClusterId, Cluster>();
|
||||
|
||||
this.clusterOrders = iconOrder;
|
||||
|
||||
// update new clusters
|
||||
clusters.forEach(clusterModel => {
|
||||
let cluster = currentClusters.get(clusterModel.id);
|
||||
@@ -182,6 +212,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
return toJS({
|
||||
activeCluster: this.activeClusterId,
|
||||
clusters: this.clustersList.map(cluster => cluster.toJSON()),
|
||||
iconOrder: this.clusterOrders,
|
||||
}, {
|
||||
recurseEverything: true
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
position: relative;
|
||||
border-radius: $radius;
|
||||
padding: $radius;
|
||||
margin-bottom: $padding * 2;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
import { ClusterIcon } from "../cluster-icon";
|
||||
import { Icon } from "../icon";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { cssNames, IClassName, autobind } from "../../utils";
|
||||
import { Badge } from "../badge";
|
||||
import { navigate } from "../../navigation";
|
||||
import { addClusterURL } from "../+add-cluster";
|
||||
@@ -20,6 +20,8 @@ import { Tooltip } from "../tooltip";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||
import { clusterViewURL, getMatchedClusterId } from "./cluster-view.route";
|
||||
import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";
|
||||
import move from "array-move";
|
||||
|
||||
// fixme: allow to rearrange clusters with drag&drop
|
||||
|
||||
@@ -87,33 +89,77 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
})
|
||||
}
|
||||
|
||||
@autobind()
|
||||
swapClusterIconOrder(result: DropResult) {
|
||||
if (result.reason === "DROP") {
|
||||
const { currentWorkspaceId } = workspaceStore;
|
||||
const clusters = clusterStore.getByWorkspaceId(currentWorkspaceId);
|
||||
|
||||
move.mutate(clusters, result.source.index, result.destination.index);
|
||||
clusterStore.clusterOrders[currentWorkspaceId] = clusters.map(c => c.id);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
const { newContexts } = userStore;
|
||||
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId);
|
||||
return (
|
||||
<div className={cssNames("ClustersMenu flex column", className)}>
|
||||
<div
|
||||
className={cssNames("ClustersMenu flex column", className)}
|
||||
onMouseEnter={() => this.showHint = false}
|
||||
>
|
||||
{showStartupHint && (
|
||||
<div className="startup-tooltip flex column gaps">
|
||||
<p><Trans>This is the quick launch menu.</Trans></p>
|
||||
<p>
|
||||
<Trans>
|
||||
Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="clusters flex column gaps">
|
||||
{clusters.map(cluster => {
|
||||
return (
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
cluster={cluster}
|
||||
isActive={cluster.id === getMatchedClusterId()}
|
||||
onClick={() => this.showCluster(cluster.id)}
|
||||
onContextMenu={() => this.showContextMenu(cluster)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<DragDropContext onDragEnd={this.swapClusterIconOrder}>
|
||||
<Droppable droppableId="cluster-menu" type="CLUSTER">
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{clusters.map((cluster, index) => (
|
||||
<Draggable draggableId={cluster.id} index={index} key={cluster.id}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
cluster={cluster}
|
||||
isActive={cluster.id === getMatchedClusterId()}
|
||||
onClick={() => this.showCluster(cluster.id)}
|
||||
onContextMenu={() => this.showContextMenu(cluster)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
<div className="add-cluster" onClick={this.addCluster}>
|
||||
<Tooltip targetId="add-cluster-icon">
|
||||
<Trans>Add Cluster</Trans>
|
||||
</Tooltip>
|
||||
<Icon big material="add" id="add-cluster-icon"/>
|
||||
<Icon big material="add" id="add-cluster-icon" />
|
||||
{newContexts.size > 0 && (
|
||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>}/>
|
||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user