rails_modal_manager 1.0.32 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +32 -1
- data/app/assets/stylesheets/rails_modal_manager.css +128 -0
- data/app/javascript/rails_modal_manager/controllers/rmm_modal_controller.js +3 -1
- data/app/javascript/rails_modal_manager/controllers/rmm_taskbar_controller.js +191 -23
- data/app/javascript/rails_modal_manager/index.js +10 -5
- data/app/javascript/rails_modal_manager/modal_store.js +90 -9
- data/lib/rails_modal_manager/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be6c4d54beffce5af66fd740e4c8d1164bab5ad19ca6ceacfb889da7ff5a908f
|
|
4
|
+
data.tar.gz: 559d900a84c1ebefde8711de82a91e7eac7521d05439e71af314e5dfd14d362c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12084926d5370e7e3bc4e4bf047696b90f3f3c962d4bb185e3eddbb40ecce88e695a1a1413b2068083db229953f40e8acda5b0f4b5eaad6dffe73f4ecf94da1c
|
|
7
|
+
data.tar.gz: 01b06a874a6bf993d2e27d3ec24604a9c10a1da90bde62d8200ee8aad53dbdeb6a1cd90e27af859bcef29acc3ad3e0ca057741afb5b93c21aaf0f555cc810e32
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Rails 애플리케이션을 위한 고급 모달 매니저입니다.
|
|
4
4
|
`@reshacs/react-modal-manager`에서 포팅되었습니다.
|
|
5
5
|
|
|
6
|
-
**Version:** 1.0.
|
|
6
|
+
**Version:** 1.0.33
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -198,6 +198,7 @@ closeModal('my-modal')
|
|
|
198
198
|
| `confirm-close-message` | String | "정말 닫으시겠습니까?" | 확인 메시지 |
|
|
199
199
|
| `mobile-default-maximized` | Boolean | false | 모바일에서 기본 최대화 |
|
|
200
200
|
| `parent-modal-id` | String | - | 부모 모달 ID (종속 관계) |
|
|
201
|
+
| `persistent-id` | String | - | 크기 상태 공유용 ID (같은 타입 모달 간 크기 공유) |
|
|
201
202
|
| `min-width` | Number | 200 | 최소 너비 (px) |
|
|
202
203
|
| `min-height` | Number | 150 | 최소 높이 (px) |
|
|
203
204
|
| `max-width` | Number | - | 최대 너비 (px) |
|
|
@@ -438,6 +439,14 @@ header_buttons: [
|
|
|
438
439
|
|
|
439
440
|
Taskbar는 자동으로 최소화된 모달들을 렌더링합니다.
|
|
440
441
|
|
|
442
|
+
**Taskbar 기능 (v1.0.33+):**
|
|
443
|
+
|
|
444
|
+
- **클릭하여 복구**: 태스크바 아이템 클릭 시 모달 복구 (X버튼 제외)
|
|
445
|
+
- **드래그 앤 드롭**: 아이템을 드래그하여 순서 변경
|
|
446
|
+
- **키보드 단축키**: Alt+1~9로 빠른 복구/최소화 토글
|
|
447
|
+
- **모두 닫기**: 2개 이상 최소화 시 "모두 닫기" 버튼 표시
|
|
448
|
+
- **확인 다이얼로그**: X버튼 클릭 시 닫기 확인
|
|
449
|
+
|
|
441
450
|
### Resize Handle
|
|
442
451
|
|
|
443
452
|
모달 리사이즈를 위한 핸들입니다.
|
|
@@ -789,6 +798,28 @@ MIT License
|
|
|
789
798
|
|
|
790
799
|
## 변경 이력
|
|
791
800
|
|
|
801
|
+
### v1.0.33
|
|
802
|
+
|
|
803
|
+
- **Taskbar 기능 대폭 개선**:
|
|
804
|
+
- 클릭하여 복구: 태스크바 아이템 클릭 시 모달 복구 (X버튼 제외)
|
|
805
|
+
- 드래그 앤 드롭: 최소화된 모달 순서 변경 지원
|
|
806
|
+
- 키보드 단축키: Alt+1~9로 빠른 복구/최소화 토글
|
|
807
|
+
- 모두 닫기 버튼: 2개 이상 최소화 시 표시 (확인 다이얼로그 포함)
|
|
808
|
+
- 개별 닫기 확인: X버튼 클릭 시 확인 다이얼로그 표시
|
|
809
|
+
- **복구된 모달 상태 표시**: 최소화에서 복구된 모달은 태스크바에서 비활성화 상태로 표시
|
|
810
|
+
- **같은 ID 모달 재오픈 버그 수정**: 최소화된 상태에서 같은 ID로 모달 열기 시 정상적으로 복구
|
|
811
|
+
|
|
812
|
+
### v1.0.32
|
|
813
|
+
|
|
814
|
+
- **`persistent_id` 옵션 추가**: 같은 타입의 모달들이 크기 상태를 공유할 수 있도록 지원
|
|
815
|
+
- 업체 모달, 사용자 프로필 모달 등에서 활용 가능
|
|
816
|
+
|
|
817
|
+
### v1.0.31
|
|
818
|
+
|
|
819
|
+
- **모달 크기 기억 기능 추가**: 모달이 마지막 크기(최대화/기본)를 localStorage에 저장하여 다음에 열 때 동일한 크기로 열림
|
|
820
|
+
- **모바일 사이드바 동작 개선**: 최대화된 모달에서 아이콘 상태는 inline(본문 공간 차지), 확장 상태는 오버레이 방식으로 변경
|
|
821
|
+
- **사이드바 3상태 토글**: expanded(확장), icons(아이콘), hidden(숨김) 상태 지원
|
|
822
|
+
|
|
792
823
|
### v1.0.24
|
|
793
824
|
|
|
794
825
|
- **`submenu_groups` 옵션 추가**: 사이드바의 각 메뉴에 서로 다른 서브메뉴 그룹을 연결 가능
|
|
@@ -1157,6 +1157,20 @@
|
|
|
1157
1157
|
background: var(--rmm-taskbar-item-hover);
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
+
.rmm-taskbar-item-disabled {
|
|
1161
|
+
opacity: 0.5;
|
|
1162
|
+
pointer-events: none;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.rmm-taskbar-item-disabled .rmm-taskbar-item-close {
|
|
1166
|
+
pointer-events: auto;
|
|
1167
|
+
opacity: 0.7;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
.rmm-taskbar-item-disabled .rmm-taskbar-item-close:hover {
|
|
1171
|
+
opacity: 1;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1160
1174
|
.rmm-taskbar-item-title {
|
|
1161
1175
|
max-width: 150px;
|
|
1162
1176
|
overflow: hidden;
|
|
@@ -1204,6 +1218,120 @@
|
|
|
1204
1218
|
height: 14px;
|
|
1205
1219
|
}
|
|
1206
1220
|
|
|
1221
|
+
/* Taskbar - Close All Button */
|
|
1222
|
+
.rmm-taskbar-close-all {
|
|
1223
|
+
display: flex;
|
|
1224
|
+
align-items: center;
|
|
1225
|
+
justify-content: center;
|
|
1226
|
+
width: 32px;
|
|
1227
|
+
height: 32px;
|
|
1228
|
+
border: none;
|
|
1229
|
+
background: rgba(239, 68, 68, 0.2);
|
|
1230
|
+
border-radius: 6px;
|
|
1231
|
+
cursor: pointer;
|
|
1232
|
+
color: #ef4444;
|
|
1233
|
+
opacity: 0.9;
|
|
1234
|
+
transition: opacity 0.15s, background-color 0.15s;
|
|
1235
|
+
flex-shrink: 0;
|
|
1236
|
+
margin-right: 4px;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.rmm-taskbar-close-all:hover {
|
|
1240
|
+
opacity: 1;
|
|
1241
|
+
background: rgba(239, 68, 68, 0.4);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.rmm-taskbar-close-all svg {
|
|
1245
|
+
width: 16px;
|
|
1246
|
+
height: 16px;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/* Taskbar - Shortcut Badge */
|
|
1250
|
+
.rmm-taskbar-shortcut {
|
|
1251
|
+
padding: 2px 5px;
|
|
1252
|
+
font-size: 10px;
|
|
1253
|
+
font-weight: 500;
|
|
1254
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1255
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1256
|
+
border-radius: 4px;
|
|
1257
|
+
font-family: monospace;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.rmm-taskbar-item:hover .rmm-taskbar-shortcut {
|
|
1261
|
+
background: rgba(255, 255, 255, 0.25);
|
|
1262
|
+
color: rgba(255, 255, 255, 0.9);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.rmm-taskbar-item-disabled .rmm-taskbar-shortcut {
|
|
1266
|
+
opacity: 0.5;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/* Taskbar - Drag Handle */
|
|
1270
|
+
.rmm-taskbar-item-drag-handle {
|
|
1271
|
+
display: flex;
|
|
1272
|
+
align-items: center;
|
|
1273
|
+
justify-content: center;
|
|
1274
|
+
width: 16px;
|
|
1275
|
+
height: 16px;
|
|
1276
|
+
cursor: grab;
|
|
1277
|
+
opacity: 0.5;
|
|
1278
|
+
transition: opacity 0.15s;
|
|
1279
|
+
flex-shrink: 0;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.rmm-taskbar-item-drag-handle:active {
|
|
1283
|
+
cursor: grabbing;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
.rmm-taskbar-item:hover .rmm-taskbar-item-drag-handle {
|
|
1287
|
+
opacity: 0.8;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
.rmm-taskbar-item-drag-handle svg {
|
|
1291
|
+
width: 12px;
|
|
1292
|
+
height: 12px;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/* Taskbar - Dragging States */
|
|
1296
|
+
.rmm-taskbar-item-dragging {
|
|
1297
|
+
opacity: 0.8;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.rmm-taskbar-item-dragging-active {
|
|
1301
|
+
opacity: 0.4;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/* Taskbar - Drop Indicators */
|
|
1305
|
+
.rmm-taskbar-item-drop-before {
|
|
1306
|
+
position: relative;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
.rmm-taskbar-item-drop-before::before {
|
|
1310
|
+
content: '';
|
|
1311
|
+
position: absolute;
|
|
1312
|
+
left: -6px;
|
|
1313
|
+
top: 4px;
|
|
1314
|
+
bottom: 4px;
|
|
1315
|
+
width: 3px;
|
|
1316
|
+
background: var(--rmm-btn-primary-bg, #3b82f6);
|
|
1317
|
+
border-radius: 2px;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.rmm-taskbar-item-drop-after {
|
|
1321
|
+
position: relative;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
.rmm-taskbar-item-drop-after::after {
|
|
1325
|
+
content: '';
|
|
1326
|
+
position: absolute;
|
|
1327
|
+
right: -6px;
|
|
1328
|
+
top: 4px;
|
|
1329
|
+
bottom: 4px;
|
|
1330
|
+
width: 3px;
|
|
1331
|
+
background: var(--rmm-btn-primary-bg, #3b82f6);
|
|
1332
|
+
border-radius: 2px;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1207
1335
|
/* ============================================
|
|
1208
1336
|
Responsive
|
|
1209
1337
|
============================================ */
|
|
@@ -495,8 +495,10 @@ export default class extends Controller {
|
|
|
495
495
|
this.applyStyles()
|
|
496
496
|
|
|
497
497
|
// Handle minimize (modal and overlay)
|
|
498
|
+
// 복구된 모달(isRestored)은 isMinimized가 true지만 화면에 표시되어야 함
|
|
498
499
|
const overlay = this.getOverlay()
|
|
499
|
-
|
|
500
|
+
const shouldMinimize = config.isMinimized && !config.isRestored
|
|
501
|
+
if (shouldMinimize) {
|
|
500
502
|
this.element.classList.add('rmm-minimized')
|
|
501
503
|
if (overlay) overlay.classList.add('rmm-minimized')
|
|
502
504
|
} else {
|
|
@@ -5,12 +5,17 @@ import historyStackManager from "rails_modal_manager/history_stack_manager"
|
|
|
5
5
|
/**
|
|
6
6
|
* Rails Modal Manager - Taskbar Controller
|
|
7
7
|
* Shows minimized modals at the bottom of the screen
|
|
8
|
+
* Supports keyboard shortcuts (Alt+1~9) for quick restore/minimize
|
|
9
|
+
* Supports drag and drop to reorder minimized modals
|
|
8
10
|
*/
|
|
9
11
|
export default class extends Controller {
|
|
10
12
|
static targets = ["container", "items"]
|
|
11
13
|
|
|
12
14
|
connect() {
|
|
13
15
|
this.unsubscribe = modalStore.subscribe(() => this.render())
|
|
16
|
+
this.handleKeyDown = this.handleKeyDown.bind(this)
|
|
17
|
+
document.addEventListener('keydown', this.handleKeyDown)
|
|
18
|
+
this.draggedIndex = null
|
|
14
19
|
this.render()
|
|
15
20
|
}
|
|
16
21
|
|
|
@@ -18,6 +23,32 @@ export default class extends Controller {
|
|
|
18
23
|
if (this.unsubscribe) {
|
|
19
24
|
this.unsubscribe()
|
|
20
25
|
}
|
|
26
|
+
document.removeEventListener('keydown', this.handleKeyDown)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
handleKeyDown(e) {
|
|
30
|
+
// Alt+1~9 단축키 처리
|
|
31
|
+
if (e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
|
32
|
+
const keyNum = parseInt(e.key, 10)
|
|
33
|
+
if (keyNum >= 1 && keyNum <= 9) {
|
|
34
|
+
const groups = modalStore.getMinimizedModalGroups()
|
|
35
|
+
const index = keyNum - 1
|
|
36
|
+
|
|
37
|
+
if (index < groups.length) {
|
|
38
|
+
e.preventDefault()
|
|
39
|
+
e.stopPropagation()
|
|
40
|
+
|
|
41
|
+
const group = groups[index]
|
|
42
|
+
if (group.isRestored) {
|
|
43
|
+
// 복구된 상태면 다시 최소화
|
|
44
|
+
modalStore.minimizeModalGroup(group.leafModalId)
|
|
45
|
+
} else {
|
|
46
|
+
// 최소화 상태면 복구
|
|
47
|
+
this.restoreByModalId(group.leafModalId)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
21
52
|
}
|
|
22
53
|
|
|
23
54
|
render() {
|
|
@@ -34,16 +65,37 @@ export default class extends Controller {
|
|
|
34
65
|
this.element.classList.add('rmm-taskbar-visible')
|
|
35
66
|
|
|
36
67
|
if (this.hasItemsTarget) {
|
|
37
|
-
|
|
68
|
+
// 최소화된 모달이 2개 이상이면 "모두 닫기" 버튼 추가
|
|
69
|
+
const closeAllBtn = groups.length >= 2 ? this.renderCloseAllButton() : ''
|
|
70
|
+
const items = groups.map((group, index) => this.renderTaskbarItem(group, index)).join('')
|
|
71
|
+
this.itemsTarget.innerHTML = closeAllBtn + items
|
|
38
72
|
}
|
|
39
73
|
}
|
|
40
74
|
|
|
41
|
-
|
|
75
|
+
renderCloseAllButton() {
|
|
76
|
+
return `
|
|
77
|
+
<button type="button" class="rmm-taskbar-close-all" data-action="click->rmm-taskbar#closeAll" title="최소화된 모달 모두 닫기">
|
|
78
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
79
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
80
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
81
|
+
</svg>
|
|
82
|
+
</button>
|
|
83
|
+
`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
renderTaskbarItem(group, index) {
|
|
42
87
|
const groupBadge = group.groupSize > 1 ? `<span class="rmm-taskbar-badge">+${group.groupSize - 1}</span>` : ''
|
|
88
|
+
const isRestored = group.isRestored
|
|
89
|
+
const disabledClass = isRestored ? ' rmm-taskbar-item-disabled' : ''
|
|
90
|
+
|
|
91
|
+
// 드래그 이벤트 + 클릭 이벤트 (복구된 상태가 아닐 때만 클릭 이벤트 추가)
|
|
92
|
+
const dragEvents = 'dragstart->rmm-taskbar#dragStart dragend->rmm-taskbar#dragEnd dragover->rmm-taskbar#dragOver drop->rmm-taskbar#drop dragenter->rmm-taskbar#dragEnter dragleave->rmm-taskbar#dragLeave'
|
|
93
|
+
const clickEvent = isRestored ? '' : ' click->rmm-taskbar#restoreItem'
|
|
94
|
+
const dataAction = `data-action="${dragEvents}${clickEvent}"`
|
|
43
95
|
|
|
44
96
|
return `
|
|
45
|
-
<div class="rmm-taskbar-item" data-modal-id="${group.leafModalId}" data-root-id="${group.rootModalId}">
|
|
46
|
-
<span class="rmm-taskbar-item-title"
|
|
97
|
+
<div class="rmm-taskbar-item${disabledClass}" data-modal-id="${group.leafModalId}" data-root-id="${group.rootModalId}" draggable="true" data-index="${index}" ${dataAction}>
|
|
98
|
+
<span class="rmm-taskbar-item-title">${this.escapeHtml(group.title)}</span>
|
|
47
99
|
${groupBadge}
|
|
48
100
|
<button type="button" class="rmm-taskbar-item-close" data-action="click->rmm-taskbar#closeGroup" data-modal-id="${group.rootModalId}" title="닫기">
|
|
49
101
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -55,36 +107,152 @@ export default class extends Controller {
|
|
|
55
107
|
`
|
|
56
108
|
}
|
|
57
109
|
|
|
110
|
+
// ============================================
|
|
111
|
+
// Drag and Drop Handlers
|
|
112
|
+
// ============================================
|
|
113
|
+
|
|
114
|
+
dragStart(e) {
|
|
115
|
+
const item = e.currentTarget
|
|
116
|
+
this.draggedIndex = parseInt(item.dataset.index, 10)
|
|
117
|
+
item.classList.add('rmm-taskbar-item-dragging')
|
|
118
|
+
|
|
119
|
+
// 드래그 이미지 설정 (반투명)
|
|
120
|
+
e.dataTransfer.effectAllowed = 'move'
|
|
121
|
+
e.dataTransfer.setData('text/plain', this.draggedIndex.toString())
|
|
122
|
+
|
|
123
|
+
// 약간의 지연 후 드래깅 클래스 추가 (드래그 이미지에 영향 안주게)
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
item.classList.add('rmm-taskbar-item-dragging-active')
|
|
126
|
+
}, 0)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
dragEnd(e) {
|
|
130
|
+
const item = e.currentTarget
|
|
131
|
+
item.classList.remove('rmm-taskbar-item-dragging', 'rmm-taskbar-item-dragging-active')
|
|
132
|
+
this.draggedIndex = null
|
|
133
|
+
|
|
134
|
+
// 모든 드롭 타겟 스타일 제거
|
|
135
|
+
this.element.querySelectorAll('.rmm-taskbar-item').forEach(el => {
|
|
136
|
+
el.classList.remove('rmm-taskbar-item-drop-before', 'rmm-taskbar-item-drop-after')
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
dragOver(e) {
|
|
141
|
+
e.preventDefault()
|
|
142
|
+
e.dataTransfer.dropEffect = 'move'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
dragEnter(e) {
|
|
146
|
+
e.preventDefault()
|
|
147
|
+
const item = e.currentTarget
|
|
148
|
+
const targetIndex = parseInt(item.dataset.index, 10)
|
|
149
|
+
|
|
150
|
+
if (this.draggedIndex === null || targetIndex === this.draggedIndex) return
|
|
151
|
+
|
|
152
|
+
// 드롭 위치 표시
|
|
153
|
+
this.element.querySelectorAll('.rmm-taskbar-item').forEach(el => {
|
|
154
|
+
el.classList.remove('rmm-taskbar-item-drop-before', 'rmm-taskbar-item-drop-after')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
if (targetIndex < this.draggedIndex) {
|
|
158
|
+
item.classList.add('rmm-taskbar-item-drop-before')
|
|
159
|
+
} else {
|
|
160
|
+
item.classList.add('rmm-taskbar-item-drop-after')
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
dragLeave(e) {
|
|
165
|
+
const item = e.currentTarget
|
|
166
|
+
// relatedTarget이 자식 요소인 경우 무시
|
|
167
|
+
if (item.contains(e.relatedTarget)) return
|
|
168
|
+
|
|
169
|
+
item.classList.remove('rmm-taskbar-item-drop-before', 'rmm-taskbar-item-drop-after')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
drop(e) {
|
|
173
|
+
e.preventDefault()
|
|
174
|
+
const item = e.currentTarget
|
|
175
|
+
const targetIndex = parseInt(item.dataset.index, 10)
|
|
176
|
+
|
|
177
|
+
// 모든 드롭 타겟 스타일 제거
|
|
178
|
+
this.element.querySelectorAll('.rmm-taskbar-item').forEach(el => {
|
|
179
|
+
el.classList.remove('rmm-taskbar-item-drop-before', 'rmm-taskbar-item-drop-after')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
if (this.draggedIndex === null || targetIndex === this.draggedIndex) return
|
|
183
|
+
|
|
184
|
+
// 순서 변경
|
|
185
|
+
modalStore.reorderMinimizedModalGroups(this.draggedIndex, targetIndex)
|
|
186
|
+
this.draggedIndex = null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================
|
|
190
|
+
// Click Handlers
|
|
191
|
+
// ============================================
|
|
192
|
+
|
|
193
|
+
restoreItem(e) {
|
|
194
|
+
// X버튼 클릭은 제외
|
|
195
|
+
if (e.target.closest('.rmm-taskbar-item-close')) {
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
e.stopPropagation()
|
|
199
|
+
const item = e.currentTarget
|
|
200
|
+
const modalId = item.dataset.modalId
|
|
201
|
+
if (modalId) {
|
|
202
|
+
this.restoreByModalId(modalId)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
58
206
|
restore(e) {
|
|
59
207
|
e.stopPropagation()
|
|
60
208
|
const modalId = e.currentTarget.dataset.modalId || e.target.closest('[data-modal-id]')?.dataset.modalId
|
|
61
209
|
if (modalId) {
|
|
62
|
-
|
|
63
|
-
const rootId = modalStore.getRootModal(modalId)
|
|
64
|
-
const descendants = modalStore.getAllDescendants(rootId)
|
|
65
|
-
const allModalIds = [rootId, ...descendants]
|
|
66
|
-
|
|
67
|
-
// Restore all modals in group
|
|
68
|
-
modalStore.restoreModalGroup(modalId)
|
|
69
|
-
|
|
70
|
-
// Re-add history entries for each modal
|
|
71
|
-
allModalIds.forEach(id => {
|
|
72
|
-
const modalElement = document.getElementById(id)
|
|
73
|
-
if (modalElement) {
|
|
74
|
-
const controller = this.application.getControllerForElementAndIdentifier(modalElement, 'rmm-modal')
|
|
75
|
-
if (controller && controller.enableHistoryStackValue && !historyStackManager.hasHisData('modal', id)) {
|
|
76
|
-
historyStackManager.addHisData('modal', id, () => controller.close('history'))
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
})
|
|
210
|
+
this.restoreByModalId(modalId)
|
|
80
211
|
}
|
|
81
212
|
}
|
|
82
213
|
|
|
214
|
+
restoreByModalId(modalId) {
|
|
215
|
+
// Re-add to history stack
|
|
216
|
+
const rootId = modalStore.getRootModal(modalId)
|
|
217
|
+
const descendants = modalStore.getAllDescendants(rootId)
|
|
218
|
+
const allModalIds = [rootId, ...descendants]
|
|
219
|
+
|
|
220
|
+
// Restore all modals in group
|
|
221
|
+
modalStore.restoreModalGroup(modalId)
|
|
222
|
+
|
|
223
|
+
// Re-add history entries for each modal
|
|
224
|
+
allModalIds.forEach(id => {
|
|
225
|
+
const modalElement = document.getElementById(id)
|
|
226
|
+
if (modalElement) {
|
|
227
|
+
const controller = this.application.getControllerForElementAndIdentifier(modalElement, 'rmm-modal')
|
|
228
|
+
if (controller && controller.enableHistoryStackValue && !historyStackManager.hasHisData('modal', id)) {
|
|
229
|
+
historyStackManager.addHisData('modal', id, () => controller.close('history'))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
83
235
|
closeGroup(e) {
|
|
84
236
|
e.stopPropagation()
|
|
85
237
|
const modalId = e.currentTarget.dataset.modalId
|
|
86
238
|
if (modalId) {
|
|
87
|
-
|
|
239
|
+
const confirmed = window.confirm('이 모달을 닫으시겠습니까?')
|
|
240
|
+
if (confirmed) {
|
|
241
|
+
modalStore.closeModalWithDescendants(modalId)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
closeAll(e) {
|
|
247
|
+
e.stopPropagation()
|
|
248
|
+
|
|
249
|
+
const groups = modalStore.getMinimizedModalGroups()
|
|
250
|
+
if (groups.length < 2) return
|
|
251
|
+
|
|
252
|
+
// 확인 다이얼로그 표시
|
|
253
|
+
const confirmed = window.confirm(`최소화된 모달 ${groups.length}개를 모두 닫으시겠습니까?`)
|
|
254
|
+
if (confirmed) {
|
|
255
|
+
modalStore.closeAllMinimizedModals()
|
|
88
256
|
}
|
|
89
257
|
}
|
|
90
258
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @license MIT
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
export const VERSION = "1.0.
|
|
11
|
+
export const VERSION = "1.0.33"
|
|
12
12
|
|
|
13
13
|
// Import core modules for internal use
|
|
14
14
|
import modalStore, { MODAL_CONSTANTS, SIZE_CONFIG, POSITION_CONFIG, CASCADE_OFFSET, modalSizeStorage, modalUtils } from "rails_modal_manager/modal_store"
|
|
@@ -87,11 +87,16 @@ export function openModal(modalId) {
|
|
|
87
87
|
const modalElement = document.getElementById(modalId)
|
|
88
88
|
if (!modalElement) return
|
|
89
89
|
|
|
90
|
-
// Check if the modal is already open
|
|
90
|
+
// Check if the modal is already open
|
|
91
91
|
const config = modalStore.getModalConfig(modalId)
|
|
92
|
-
if (config
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
if (config) {
|
|
93
|
+
if (config.isMinimized && !config.isRestored) {
|
|
94
|
+
// 최소화 상태(비활성화 아님)면 복구
|
|
95
|
+
modalStore.restoreModalGroup(modalId)
|
|
96
|
+
} else {
|
|
97
|
+
// 이미 열려있거나 복구된 상태면 앞으로 가져오기
|
|
98
|
+
modalStore.bringToFront(modalId)
|
|
99
|
+
}
|
|
95
100
|
return
|
|
96
101
|
}
|
|
97
102
|
|
|
@@ -86,6 +86,15 @@ class ModalStore {
|
|
|
86
86
|
|
|
87
87
|
openModal(modalId, config) {
|
|
88
88
|
if (this.activeModals[modalId]) {
|
|
89
|
+
// 이미 열려있는 모달 처리
|
|
90
|
+
const existingConfig = this.activeModals[modalId];
|
|
91
|
+
if (existingConfig.isMinimized && !existingConfig.isRestored) {
|
|
92
|
+
// 최소화 상태(비활성화 아님)면 복구
|
|
93
|
+
this.restoreModalGroup(modalId);
|
|
94
|
+
} else {
|
|
95
|
+
// 이미 열려있거나 복구된 상태면 앞으로 가져오기
|
|
96
|
+
this.bringToFront(modalId);
|
|
97
|
+
}
|
|
89
98
|
return;
|
|
90
99
|
}
|
|
91
100
|
|
|
@@ -368,13 +377,68 @@ class ModalStore {
|
|
|
368
377
|
this.notify();
|
|
369
378
|
}
|
|
370
379
|
|
|
380
|
+
closeAllMinimizedModals() {
|
|
381
|
+
const groups = this.getMinimizedModalGroups();
|
|
382
|
+
// 모든 최소화된 그룹을 닫음
|
|
383
|
+
groups.forEach(group => {
|
|
384
|
+
this.closeModalWithDescendants(group.rootModalId);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 단축키용: 특정 인덱스의 최소화된 모달 그룹 토글 (복구/최소화)
|
|
389
|
+
toggleMinimizedModalByIndex(index) {
|
|
390
|
+
const groups = this.getMinimizedModalGroups();
|
|
391
|
+
if (index < 0 || index >= groups.length) return false;
|
|
392
|
+
|
|
393
|
+
const group = groups[index];
|
|
394
|
+
if (group.isRestored) {
|
|
395
|
+
// 복구된 상태면 다시 최소화
|
|
396
|
+
this.minimizeModalGroup(group.leafModalId);
|
|
397
|
+
} else {
|
|
398
|
+
// 최소화 상태면 복구
|
|
399
|
+
this.restoreModalGroup(group.leafModalId);
|
|
400
|
+
}
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 최소화된 모달 그룹 순서 변경 (드래그앤드롭용)
|
|
405
|
+
reorderMinimizedModalGroups(fromIndex, toIndex) {
|
|
406
|
+
const groups = this.getMinimizedModalGroups();
|
|
407
|
+
if (fromIndex < 0 || fromIndex >= groups.length) return false;
|
|
408
|
+
if (toIndex < 0 || toIndex >= groups.length) return false;
|
|
409
|
+
if (fromIndex === toIndex) return false;
|
|
410
|
+
|
|
411
|
+
// 순서를 변경하기 위해 minimizedAt 타임스탬프 재할당
|
|
412
|
+
// 기존 순서에서 fromIndex 항목을 toIndex 위치로 이동
|
|
413
|
+
const reordered = [...groups];
|
|
414
|
+
const [moved] = reordered.splice(fromIndex, 1);
|
|
415
|
+
reordered.splice(toIndex, 0, moved);
|
|
416
|
+
|
|
417
|
+
// 새로운 순서에 맞게 minimizedAt 타임스탬프 재할당
|
|
418
|
+
const baseTime = Date.now();
|
|
419
|
+
reordered.forEach((group, index) => {
|
|
420
|
+
const allModalIds = group.modalIds;
|
|
421
|
+
const newTimestamp = baseTime + index;
|
|
422
|
+
|
|
423
|
+
allModalIds.forEach(id => {
|
|
424
|
+
if (this.activeModals[id]) {
|
|
425
|
+
this.activeModals[id].minimizedAt = newTimestamp;
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
this.notify();
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
371
434
|
// ============================================
|
|
372
435
|
// Body Scroll Management
|
|
373
436
|
// ============================================
|
|
374
437
|
|
|
375
438
|
hasVisibleOverlayModals() {
|
|
376
439
|
return Object.values(this.activeModals).some(
|
|
377
|
-
|
|
440
|
+
// 복구된 모달(isRestored)은 화면에 보이므로 visible로 처리
|
|
441
|
+
config => (!config.isMinimized || config.isRestored) && !config.hideOverlay
|
|
378
442
|
);
|
|
379
443
|
}
|
|
380
444
|
|
|
@@ -447,14 +511,29 @@ class ModalStore {
|
|
|
447
511
|
const rootId = this.getRootModal(modalId);
|
|
448
512
|
const descendants = this.getAllDescendants(rootId);
|
|
449
513
|
const allModalIds = [rootId, ...descendants];
|
|
450
|
-
const minimizedAt = Date.now();
|
|
451
514
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
515
|
+
// 이미 최소화 상태인지 확인 (복구 후 다시 최소화하는 경우)
|
|
516
|
+
const rootConfig = this.activeModals[rootId];
|
|
517
|
+
const wasRestored = rootConfig?.isRestored;
|
|
518
|
+
|
|
519
|
+
if (wasRestored) {
|
|
520
|
+
// 복구 상태였다면 isRestored만 false로 변경 (위치 유지)
|
|
521
|
+
allModalIds.forEach(id => {
|
|
522
|
+
if (this.activeModals[id]) {
|
|
523
|
+
this.activeModals[id].isRestored = false;
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
} else {
|
|
527
|
+
// 새로운 최소화인 경우
|
|
528
|
+
const minimizedAt = Date.now();
|
|
529
|
+
allModalIds.forEach(id => {
|
|
530
|
+
if (this.activeModals[id]) {
|
|
531
|
+
this.activeModals[id].isMinimized = true;
|
|
532
|
+
this.activeModals[id].isRestored = false;
|
|
533
|
+
this.activeModals[id].minimizedAt = minimizedAt;
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}
|
|
458
537
|
|
|
459
538
|
this.updateBodyScroll();
|
|
460
539
|
this.notify();
|
|
@@ -466,9 +545,10 @@ class ModalStore {
|
|
|
466
545
|
const descendants = this.getAllDescendants(rootId);
|
|
467
546
|
const allModalIds = [rootId, ...descendants];
|
|
468
547
|
|
|
548
|
+
// isMinimized는 유지하고 isRestored를 true로 설정 (태스크바에 비활성화 상태로 유지)
|
|
469
549
|
allModalIds.forEach(id => {
|
|
470
550
|
if (this.activeModals[id]) {
|
|
471
|
-
this.activeModals[id].
|
|
551
|
+
this.activeModals[id].isRestored = true;
|
|
472
552
|
}
|
|
473
553
|
});
|
|
474
554
|
|
|
@@ -515,6 +595,7 @@ class ModalStore {
|
|
|
515
595
|
zIndex: leafConfig?.zIndex || 0,
|
|
516
596
|
modalIds: allModalIds,
|
|
517
597
|
minimizedAt: rootConfig?.minimizedAt || 0,
|
|
598
|
+
isRestored: rootConfig?.isRestored || false, // 복구된 상태인지 (비활성화 표시용)
|
|
518
599
|
};
|
|
519
600
|
});
|
|
520
601
|
|