Skip to content
Open
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
1 change: 0 additions & 1 deletion configure/public/toolConfigs.json

This file was deleted.

1 change: 1 addition & 0 deletions src/essence/Basics/UserInterface_/UserInterfaceModern_.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
gap: var(--theme-spacing-1, 0.5rem);
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-bottom: var(--theme-border-width-sm, 1px) solid rgba(0, 0, 0, 0.05);
}

/* Tabs */
Expand Down
1 change: 0 additions & 1 deletion src/essence/Tools/LayerManager/LayerManagerTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ const LayerManagerTool = {
)
return
}
$(container).css('background', 'var(--color-k)')
_root = createRoot(container)
_root.render(<MMGISLayerManagerAdapter />)
this.made = true
Expand Down
117 changes: 91 additions & 26 deletions src/essence/Tools/MODERN_TOOL_PATTERN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,84 @@ This guide explains how to create MMGIS tools compatible with the **modern layou

**Modern tools enable**:
- Dynamic placement in any DOM container
- Multiple instances of the same tool
- Cleaner lifecycle management
- Dashboard and custom panel layouts

---

## 4-Step Checklist for Modern Tools
## Tool Architecture Types

Follow these steps to make your tool compatible with modern layouts:
### 1. **Legacy jQuery Tools** (Classic Layout)
- Hardcoded to render in `#toolPanel`
- No `targetId` support
- Global event handlers

### 1. Add Core State Properties
Add `targetId` and `made` to your tool object to track its instance state:
### 3. **Modern React Tools** (Modern Layout)
- Supports dynamic `targetId` placement
- Uses React for rendering
- Component-based architecture
- Built-in cleanup and lifecycle

---

## Modern React Tool Pattern (Recommended)

For new tools, use React with TypeScript/JSX:

### Core Structure

```typescript
import React from 'react'
import { createRoot, Root } from 'react-dom/client'

let _root: Root | null = null

const MyTool = {
height: 0,
width: 300,
targetId: null as string | null,
made: false,

make: function (targetId?: string) {
this.targetId = typeof targetId === 'string' ? targetId : 'toolPanel'
const container = document.getElementById(this.targetId)

if (!container) {
console.error(`MyTool: container ${this.targetId} not found`)
return
}

_root = createRoot(container)
_root.render(<MyToolComponent />)
this.made = true
},

destroy: function () {
if (_root) {
_root.unmount()
_root = null
}
this.targetId = null
this.made = false
},

getUrlString: function () {
return ''
}
}

export default MyTool
```

---

## Modern jQuery Tool Pattern

If your tool is simple and doesn't need React, use this jQuery pattern:

### 4-Step Checklist

#### 1. Add Core State Properties

```javascript
const MyTool = {
Expand All @@ -27,56 +93,55 @@ const MyTool = {
}
```

### 2. Update `make()` to Accept `targetId`
Modify the `make()` method to accept and store the `targetId` parameter. Default to `'toolPanel'` if invalid:
#### 2. Update `make()` to Accept `targetId`

```javascript
make: function (targetId) {
this.targetId = typeof targetId === 'string' ? targetId : 'toolPanel'

this.MMGISInterface = new interfaceWithMMGIS()
this.made = true
}
```

### 3. Use Dynamic DOM Targeting and Scoping
Replace hardcoded selectors (like `#toolPanel` or `#tools`) with the dynamic `targetId`. Ensure all DOM operations and event listeners are scoped to this container to prevent conflicts with other instances.
#### 3. Use Dynamic DOM Targeting

**IMPORTANT**: All DOM operations must use `this.targetId`, not hardcoded `#toolPanel`:

```javascript
function interfaceWithMMGIS() {
// 1. Target the specific container
// 1. Get the target container
const tools = $(`#${MyTool.targetId}`)

// 2. Render content
tools.css('background', 'var(--color-k)')
tools.html('<div style="height: 100%">' + markup + '</div>')
// 2. Render your markup
tools.html(markup)

// 3. Scope event handlers using .find() or event delegation on 'tools'
tools.find('.myTool-button').on('click', function() { ... })
// OR
tools.on('click', '.myTool-button', function() { ... })
// 3. Attach event handlers scoped to this container
// This ensures they're cleaned up when the tool is destroyed
tools.find('.myTool-button').on('click', function() {
// Handle click
})
}
```

### 4. Clean Up in `destroy()`
Ensure you clean up the container, remove event listeners, and reset the state:
**Why scope events to the container?**
- Prevents memory leaks when tool is destroyed
- Allows multiple instances without conflicts
- Makes cleanup simple: `tools.off()` removes all handlers

#### 4. Clean Up in `destroy()`

```javascript
destroy: function () {
// Clean up DOM and events
this.MMGISInterface.separateFromMMGIS()

// Reset state
this.targetId = null
this.made = false
}

// Inside interfaceWithMMGIS:
function separateFromMMGIS() {
const tools = $(`#${MyTool.targetId}`)

tools.off() // Remove event listeners
tools.empty() // Clear DOM
tools.off() // Remove all event listeners
tools.empty() // Clear DOM content
}
```

Expand Down
147 changes: 136 additions & 11 deletions src/essence/Tools/Title/TitleTool.css
Original file line number Diff line number Diff line change
@@ -1,51 +1,176 @@
/* Outer container */
#titleTool {
display: flex;
padding: var(--theme-spacing-105, 0.75rem) var(--theme-spacing-2, 1rem);
flex-direction: column;
align-items: flex-start;
gap: var(--theme-spacing-205, 1.25rem);
align-self: stretch;
height: 100%;
width: 100%;
}

/* Content container */
.titleTool-container {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
gap: var(--theme-spacing-1, 0.5rem);
align-self: stretch;
flex-wrap: wrap;
}

.titleTool-container {
/* Icon & text combined container */
.titleTool-header {
display: flex;
align-items: center;
gap: 12px;
gap: var(--theme-spacing-105, 0.75rem);
flex: 0 0 auto;
min-width: 0;
}

/* Logo container */
.titleTool-logo {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
max-height: 3.25rem;
flex-shrink: 0;
}

.titleTool-logo-img {
height: 40px;
height: 100%;
width: auto;
max-width: 150px;
max-height: 3.25rem;
object-fit: contain;
}

/* Icon container */
.titleTool-icon {
display: flex;
align-items: center;
justify-content: center;
color: var(--color-a);
width: clamp(2.5rem, 3.25rem, 3.25rem);
height: clamp(2.5rem, 3.25rem, 3.25rem);
flex-shrink: 0;
color: var(--theme-color-primary, var(--color-a));
}

.titleTool-icon i {
font-size: 24px;
font-size: clamp(1.5rem, 2rem, 2rem);
}

/* Text container */
.titleTool-text {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-width: 0;
}

.titleTool-title {
font-size: 18px;
font-weight: 600;
color: var(--color-a);
color: var(--theme-color-primary, var(--color-a));
font-family: var(--theme-font-ui, "Public Sans", sans-serif);
font-size: var(--theme-font-size-lg, 1.125rem);
font-weight: var(--theme-font-weight-semibold, 600);
line-height: var(--theme-line-height-2, 1.35);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

/* Action button container */
.titleTool-action-container {
display: flex;
min-height: var(--theme-spacing-5, 2.5rem);
justify-content: flex-end;
align-items: center;
gap: var(--theme-spacing-1, 0.5rem);
flex: 1 0 auto;
margin-left: auto;
}

/* Actual button */
.titleTool-action-button {
display: flex;
height: var(--theme-spacing-4, 2.25rem);
padding: var(--theme-spacing-05, 0.25rem) var(--theme-spacing-105, 0.75rem);
justify-content: center;
align-items: center;
gap: var(--theme-spacing-1, 0.5rem);
background: var(--theme-color-primary, var(--color-a));
color: var(--theme-color-white, #FFF);
border: none;
border-radius: var(--theme-radius-sm, 0.25rem);
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
font-family: var(--theme-font-ui, "Public Sans", sans-serif);
font-size: var(--theme-font-size-xs, 0.75rem);
font-style: normal;
font-weight: var(--theme-font-weight-bold, 700);
line-height: var(--theme-line-height-1, 1);
white-space: nowrap;
}

.titleTool-action-button:hover {
background: var(--theme-color-primary-dark, var(--color-a1));
transform: translateX(2px);
}

.titleTool-action-button:active {
transform: translateX(0);
background: var(--theme-color-primary-darker, var(--color-a1));
}

.titleTool-action-button:focus {
outline: var(--theme-border-width-md, 2px) solid var(--theme-color-accent-cool, var(--color-a));
outline-offset: var(--theme-spacing-05, 0.25rem);
}

.titleTool-action-button i {
font-size: var(--theme-font-size-md, 1rem);
}

/* Responsive adjustments */
@media (max-width: 640px) {
#titleTool {
padding: var(--theme-spacing-1, 0.5rem) var(--theme-spacing-105, 0.75rem);
gap: var(--theme-spacing-1, 0.5rem);
}

.titleTool-container {
flex-wrap: wrap;
}

.titleTool-header {
flex: 1 1 100%;
}

.titleTool-action-container {
flex: 1 1 100%;
justify-content: flex-start;
margin-left: 0;
}

.titleTool-logo {
max-height: 2rem;
}

.titleTool-logo-img {
max-height: 2rem;
}

.titleTool-icon {
width: 2rem;
height: 2rem;
}

.titleTool-icon i {
font-size: 1.5rem;
}

.titleTool-title {
font-size: var(--theme-font-size-md, 1rem);
}
}
Loading