create-mendix-widget-gleam 3.0.2 → 4.0.0
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.
- package/README.md +8 -9
- package/package.json +1 -1
- package/src/i18n.mjs +12 -15
- package/src/index.mjs +20 -31
- package/src/templates/claude_md.mjs +10 -8
- package/src/templates/readme_md.mjs +60 -69
- package/src/templates/widgets_readme.mjs +15 -15
- package/template/docs/glendix_guide.md +197 -96
- package/template/docs/mendraw_guide.md +671 -0
- package/template/gleam.toml +3 -1
- package/template/package.json +1 -6
- package/template/src/__widget_name__.gleam +1 -1
- package/template/src/editor_config.gleam +1 -1
- package/template/src/editor_preview.gleam +1 -1
- package/template/rollup.config.mjs +0 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# glendix
|
|
1
|
+
# glendix v4.0.2 — Agent Reference Guide
|
|
2
2
|
|
|
3
3
|
> 이 문서는 AI 에이전트(LLM)가 glendix 코드를 작성할 때 참조하는 가이드입니다. 각 섹션은 독립적으로 읽을 수 있습니다.
|
|
4
4
|
|
|
@@ -14,9 +14,10 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
|
|
|
14
14
|
|--------|------------|-------------|
|
|
15
15
|
| React 바인딩 (엘리먼트, 훅, 이벤트, HTML/SVG) | `redraw`, `redraw_dom` | 사용하지 않음 — 직접 import |
|
|
16
16
|
| TEA 패턴 (Model-Update-View) | `lustre` | `glendix/lustre` 브릿지 제공 |
|
|
17
|
-
| Mendix API (JsProps, EditableValue, ListValue 등) | `
|
|
18
|
-
| 외부 JS 컴포넌트 (widget, binding) → React | `
|
|
19
|
-
|
|
|
17
|
+
| Mendix API (JsProps, EditableValue, ListValue 등) | `mendraw` | 핵심 담당 |
|
|
18
|
+
| 외부 JS 컴포넌트 (widget, binding) → React | `mendraw/interop` | 브릿지 제공 |
|
|
19
|
+
| Mendix 위젯 (.mpk, Classic, 마켓플레이스) | `mendraw` | 핵심 담당 |
|
|
20
|
+
| 빌드/설치 | `glendix` | 핵심 담당 |
|
|
20
21
|
|
|
21
22
|
**의존성 구조:**
|
|
22
23
|
|
|
@@ -25,13 +26,15 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
|
|
|
25
26
|
├── redraw ← React 훅, 컴포넌트, fragment 등
|
|
26
27
|
├── redraw_dom ← HTML/SVG 태그, 속성, 이벤트
|
|
27
28
|
├── lustre ← TEA update/view (선택)
|
|
29
|
+
├── mendraw
|
|
30
|
+
│ ├── mendix ← Mendix API 타입 + props 접근
|
|
31
|
+
│ ├── interop ← 외부 JS 컴포넌트 → redraw.Element
|
|
32
|
+
│ ├── widget ← .mpk 위젯 컴포넌트 (gleam.toml 자동 다운로드)
|
|
33
|
+
│ ├── classic ← Classic (Dojo) 위젯
|
|
34
|
+
│ └── marketplace ← Mendix Marketplace 검색/다운로드
|
|
28
35
|
└── glendix
|
|
29
|
-
├── mendix ← Mendix API 타입 + props 접근
|
|
30
|
-
├── interop ← 외부 JS 컴포넌트 → redraw.Element
|
|
31
36
|
├── lustre ← Lustre Element → redraw.Element 브릿지
|
|
32
|
-
├──
|
|
33
|
-
├── binding ← bindings.json 외부 React 컴포넌트
|
|
34
|
-
├── classic ← Classic (Dojo) 위젯
|
|
37
|
+
├── binding ← 외부 React 컴포넌트 (gleam.toml [tools.glendix.bindings])
|
|
35
38
|
└── js/* ← JS interop escape hatch
|
|
36
39
|
```
|
|
37
40
|
|
|
@@ -43,14 +46,15 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
|
|
|
43
46
|
|
|
44
47
|
```toml
|
|
45
48
|
[dependencies]
|
|
46
|
-
glendix = ">=
|
|
49
|
+
glendix = ">= 4.0.2 and < 5.0.0"
|
|
50
|
+
mendraw = ">= 1.1.10 and < 2.0.0"
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
Peer dependency (위젯 프로젝트 `package.json`):
|
|
50
54
|
|
|
51
55
|
```json
|
|
52
56
|
{
|
|
53
|
-
"dependencies": { "big.js": "^6.0.0" },
|
|
57
|
+
"dependencies": { "big.js": "^6.0.0" }, // decimal 속성 사용 시
|
|
54
58
|
"overrides": { "react": "19.0.0", "react-dom": "19.0.0", "@types/react": "19.0.0", "@types/react-dom": "19.0.0" },
|
|
55
59
|
"resolutions": { "react": "19.0.0", "react-dom": "19.0.0", "@types/react": "19.0.0", "@types/react-dom": "19.0.0" }
|
|
56
60
|
}
|
|
@@ -60,7 +64,7 @@ Peer dependency (위젯 프로젝트 `package.json`):
|
|
|
60
64
|
> - `overrides`/`resolutions`에서 반드시 **캐럿(`^`) 없이 정확한 버전**을 지정한다. `^19.0.0`은 react와 react-dom이 서로 다른 19.x.x로 해석되어 런타임 버전 불일치 에러를 일으킨다.
|
|
61
65
|
|
|
62
66
|
```bash
|
|
63
|
-
gleam run -m glendix/install # 의존성 설치 + 바인딩 생성
|
|
67
|
+
gleam run -m glendix/install # 의존성 설치 + TOML 위젯 다운로드 + 바인딩 생성
|
|
64
68
|
gleam build # 컴파일 확인
|
|
65
69
|
```
|
|
66
70
|
|
|
@@ -71,13 +75,13 @@ gleam build # 컴파일 확인
|
|
|
71
75
|
모든 Mendix Pluggable Widget은 이 시그니처를 따릅니다:
|
|
72
76
|
|
|
73
77
|
```gleam
|
|
74
|
-
import
|
|
78
|
+
import mendraw/mendix.{type JsProps}
|
|
75
79
|
import redraw.{type Element}
|
|
76
80
|
|
|
77
81
|
pub fn widget(props: JsProps) -> Element
|
|
78
82
|
```
|
|
79
83
|
|
|
80
|
-
- `JsProps` — Mendix가 전달하는 props 객체 (opaque). `
|
|
84
|
+
- `JsProps` — Mendix가 전달하는 props 객체 (opaque). `mendraw/mendix` 모듈의 접근자로만 읽는다.
|
|
81
85
|
- `Element` — redraw의 React 엘리먼트 타입. `redraw/dom/html`, `redraw.fragment()` 등으로 생성한다.
|
|
82
86
|
|
|
83
87
|
---
|
|
@@ -103,7 +107,7 @@ glendix는 두 가지 렌더링 경로를 지원합니다. 둘 다 `redraw.Eleme
|
|
|
103
107
|
### 5.1 필수 import 패턴
|
|
104
108
|
|
|
105
109
|
```gleam
|
|
106
|
-
import
|
|
110
|
+
import mendraw/mendix.{type JsProps} // Mendix props 타입
|
|
107
111
|
import redraw.{type Element} // 반환 타입
|
|
108
112
|
import redraw/dom/html // HTML 태그 함수
|
|
109
113
|
import redraw/dom/attribute // HTML 속성
|
|
@@ -311,7 +315,7 @@ svg.svg([attribute.attribute("viewBox", "0 0 100 100")], [
|
|
|
311
315
|
```gleam
|
|
312
316
|
import gleam/int
|
|
313
317
|
import glendix/lustre as gl
|
|
314
|
-
import
|
|
318
|
+
import mendraw/mendix.{type JsProps}
|
|
315
319
|
import lustre/effect
|
|
316
320
|
import lustre/element/html
|
|
317
321
|
import lustre/event
|
|
@@ -411,27 +415,24 @@ fn view(model: Model) {
|
|
|
411
415
|
|
|
412
416
|
| 컴포넌트 출처 | 사용 모듈 | 예시 |
|
|
413
417
|
|--------------|----------|------|
|
|
414
|
-
| npm 패키지 (React 컴포넌트) | `glendix/binding` + `
|
|
415
|
-
| `.mpk` Pluggable 위젯 | `
|
|
416
|
-
| `.mpk` Classic (Dojo) 위젯 | `
|
|
418
|
+
| npm 패키지 (React 컴포넌트) | `glendix/binding` + `mendraw/interop` | recharts, @mui |
|
|
419
|
+
| `.mpk` Pluggable 위젯 (gleam.toml) | `mendraw/widget` + `mendraw/interop` | Switch, Badge |
|
|
420
|
+
| `.mpk` Classic (Dojo) 위젯 (gleam.toml) | `mendraw/classic` | CameraWidget |
|
|
417
421
|
|
|
418
422
|
### 7.2 외부 React 컴포넌트 (binding + interop)
|
|
419
423
|
|
|
420
|
-
**설정:** `
|
|
424
|
+
**설정:** `gleam.toml`에 바인딩 추가 → `npm install 패키지명` → `gleam run -m glendix/install`
|
|
421
425
|
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
"components": ["PieChart", "Pie", "Cell", "Tooltip"]
|
|
426
|
-
}
|
|
427
|
-
}
|
|
426
|
+
```toml
|
|
427
|
+
[tools.glendix.bindings]
|
|
428
|
+
recharts = ["PieChart", "Pie", "Cell", "Tooltip"]
|
|
428
429
|
```
|
|
429
430
|
|
|
430
431
|
**Gleam 래퍼 작성:**
|
|
431
432
|
|
|
432
433
|
```gleam
|
|
433
434
|
import glendix/binding
|
|
434
|
-
import
|
|
435
|
+
import mendraw/interop
|
|
435
436
|
import redraw.{type Element}
|
|
436
437
|
import redraw/dom/attribute.{type Attribute}
|
|
437
438
|
|
|
@@ -456,15 +457,24 @@ pub fn tooltip(attrs: List(Attribute)) -> Element {
|
|
|
456
457
|
|
|
457
458
|
### 7.3 .mpk Pluggable 위젯 (widget + interop)
|
|
458
459
|
|
|
459
|
-
|
|
460
|
+
`gleam.toml`에 위젯을 등록하고 `gleam run -m glendix/install`로 자동 다운로드합니다:
|
|
461
|
+
|
|
462
|
+
```toml
|
|
463
|
+
[tools.mendraw.widgets.Charts]
|
|
464
|
+
version = "3.0.0"
|
|
465
|
+
# s3_id = "com/..." ← 있으면 인증 없이 직접 다운로드
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
`build/widgets/`에 캐시하고 바인딩을 자동 생성합니다.
|
|
469
|
+
Marketplace TUI(`gleam run -m mendraw/marketplace`)에서 다운로드하면 gleam.toml에 자동 추가됩니다.
|
|
460
470
|
|
|
461
471
|
자동 생성되는 `src/widgets/*.gleam`:
|
|
462
472
|
|
|
463
473
|
```gleam
|
|
464
|
-
import
|
|
465
|
-
import
|
|
466
|
-
import
|
|
467
|
-
import
|
|
474
|
+
import mendraw/interop
|
|
475
|
+
import mendraw/mendix
|
|
476
|
+
import mendraw/mendix.{type JsProps}
|
|
477
|
+
import mendraw/widget
|
|
468
478
|
import redraw.{type Element}
|
|
469
479
|
import redraw/dom/attribute
|
|
470
480
|
|
|
@@ -486,8 +496,8 @@ pub fn render(props: JsProps) -> Element {
|
|
|
486
496
|
| `widget.action_prop(key, handler)` | ActionValue | 액션 콜백 (onClick 등) |
|
|
487
497
|
|
|
488
498
|
```gleam
|
|
489
|
-
import
|
|
490
|
-
import
|
|
499
|
+
import mendraw/widget
|
|
500
|
+
import mendraw/interop
|
|
491
501
|
|
|
492
502
|
let comp = widget.component("Badge button")
|
|
493
503
|
interop.component_el(comp, [
|
|
@@ -503,7 +513,7 @@ interop.component_el(comp, [
|
|
|
503
513
|
|
|
504
514
|
```gleam
|
|
505
515
|
import gleam/dynamic
|
|
506
|
-
import
|
|
516
|
+
import mendraw/classic
|
|
507
517
|
|
|
508
518
|
classic.render("CameraWidget.widget.CameraWidget", [
|
|
509
519
|
#("mfToExecute", classic.to_dynamic(mf_value)),
|
|
@@ -517,12 +527,12 @@ classic.render("CameraWidget.widget.CameraWidget", [
|
|
|
517
527
|
|
|
518
528
|
## 8. Mendix API 레퍼런스
|
|
519
529
|
|
|
520
|
-
### 8.1 Props 접근 (`
|
|
530
|
+
### 8.1 Props 접근 (`mendraw/mendix`)
|
|
521
531
|
|
|
522
532
|
`JsProps`는 opaque 타입입니다. 접근자 함수로만 읽습니다.
|
|
523
533
|
|
|
524
534
|
```gleam
|
|
525
|
-
import
|
|
535
|
+
import mendraw/mendix
|
|
526
536
|
|
|
527
537
|
// Option 반환 (undefined → None)
|
|
528
538
|
mendix.get_prop(props, "myAttr") // Option(a)
|
|
@@ -542,7 +552,7 @@ mendix.has_prop(props, "onClick") // Bool
|
|
|
542
552
|
Mendix의 모든 동적 값은 상태를 가집니다:
|
|
543
553
|
|
|
544
554
|
```gleam
|
|
545
|
-
import
|
|
555
|
+
import mendraw/mendix.{Available, Loading, Unavailable}
|
|
546
556
|
|
|
547
557
|
case mendix.get_status(some_value) {
|
|
548
558
|
Available -> // 값 사용 가능
|
|
@@ -551,12 +561,12 @@ case mendix.get_status(some_value) {
|
|
|
551
561
|
}
|
|
552
562
|
```
|
|
553
563
|
|
|
554
|
-
### 8.3 EditableValue (`
|
|
564
|
+
### 8.3 EditableValue (`mendraw/mendix/editable_value`)
|
|
555
565
|
|
|
556
566
|
텍스트, 숫자, 날짜 등 편집 가능한 Mendix 속성:
|
|
557
567
|
|
|
558
568
|
```gleam
|
|
559
|
-
import
|
|
569
|
+
import mendraw/mendix/editable_value as ev
|
|
560
570
|
|
|
561
571
|
// 읽기
|
|
562
572
|
ev.value(attr) // Option(a)
|
|
@@ -581,12 +591,12 @@ ev.set_validator(attr, Some(fn(value) {
|
|
|
581
591
|
ev.universe(attr) // Option(List(a))
|
|
582
592
|
```
|
|
583
593
|
|
|
584
|
-
### 8.4 ActionValue (`
|
|
594
|
+
### 8.4 ActionValue (`mendraw/mendix/action`)
|
|
585
595
|
|
|
586
596
|
Mendix 마이크로플로우/나노플로우 실행:
|
|
587
597
|
|
|
588
598
|
```gleam
|
|
589
|
-
import
|
|
599
|
+
import mendraw/mendix/action
|
|
590
600
|
|
|
591
601
|
action.execute(my_action) // 직접 실행
|
|
592
602
|
action.execute_if_can(my_action) // can_execute가 True일 때만
|
|
@@ -596,24 +606,24 @@ action.can_execute(my_action) // Bool
|
|
|
596
606
|
action.is_executing(my_action) // Bool
|
|
597
607
|
```
|
|
598
608
|
|
|
599
|
-
### 8.5 DynamicValue (`
|
|
609
|
+
### 8.5 DynamicValue (`mendraw/mendix/dynamic_value`)
|
|
600
610
|
|
|
601
611
|
읽기 전용 표현식 속성:
|
|
602
612
|
|
|
603
613
|
```gleam
|
|
604
|
-
import
|
|
614
|
+
import mendraw/mendix/dynamic_value as dv
|
|
605
615
|
|
|
606
616
|
dv.value(expr) // Option(a)
|
|
607
617
|
dv.status(expr) // String
|
|
608
618
|
dv.is_available(expr) // Bool
|
|
609
619
|
```
|
|
610
620
|
|
|
611
|
-
### 8.6 ListValue (`
|
|
621
|
+
### 8.6 ListValue (`mendraw/mendix/list_value`)
|
|
612
622
|
|
|
613
623
|
Mendix 데이터 소스 리스트:
|
|
614
624
|
|
|
615
625
|
```gleam
|
|
616
|
-
import
|
|
626
|
+
import mendraw/mendix/list_value as lv
|
|
617
627
|
|
|
618
628
|
// 아이템 접근
|
|
619
629
|
lv.items(list_val) // Option(List(ObjectItem))
|
|
@@ -641,12 +651,12 @@ lv.set_filter(list_val, None) // 필터 해제
|
|
|
641
651
|
lv.reload(list_val)
|
|
642
652
|
```
|
|
643
653
|
|
|
644
|
-
### 8.7 ListAttribute (`
|
|
654
|
+
### 8.7 ListAttribute (`mendraw/mendix/list_attribute`)
|
|
645
655
|
|
|
646
656
|
리스트의 각 아이템에서 속성/액션/위젯 추출:
|
|
647
657
|
|
|
648
658
|
```gleam
|
|
649
|
-
import
|
|
659
|
+
import mendraw/mendix/list_attribute as la
|
|
650
660
|
|
|
651
661
|
la.get_attribute(attr, item) // EditableValue 반환
|
|
652
662
|
la.get_action(action, item) // Option(ActionValue)
|
|
@@ -661,10 +671,10 @@ la.attr_type(attr) // "String", "Integer" 등
|
|
|
661
671
|
la.attr_formatter(attr) // ValueFormatter
|
|
662
672
|
```
|
|
663
673
|
|
|
664
|
-
### 8.8 Selection (`
|
|
674
|
+
### 8.8 Selection (`mendraw/mendix/selection`)
|
|
665
675
|
|
|
666
676
|
```gleam
|
|
667
|
-
import
|
|
677
|
+
import mendraw/mendix/selection
|
|
668
678
|
|
|
669
679
|
// 단일 선택
|
|
670
680
|
selection.selection(single_sel) // Option(ObjectItem)
|
|
@@ -679,8 +689,8 @@ selection.set_selections(multi_sel, [item1, item2])
|
|
|
679
689
|
### 8.9 Reference / ReferenceSet
|
|
680
690
|
|
|
681
691
|
```gleam
|
|
682
|
-
import
|
|
683
|
-
import
|
|
692
|
+
import mendraw/mendix/reference as ref
|
|
693
|
+
import mendraw/mendix/reference_set as ref_set
|
|
684
694
|
|
|
685
695
|
// 단일 참조
|
|
686
696
|
ref.value(my_ref) // Option(a)
|
|
@@ -693,10 +703,10 @@ ref_set.value(my_ref_set) // Option(List(a))
|
|
|
693
703
|
ref_set.set_value(my_ref_set, Some([item1, item2]))
|
|
694
704
|
```
|
|
695
705
|
|
|
696
|
-
### 8.10 Filter (`
|
|
706
|
+
### 8.10 Filter (`mendraw/mendix/filter`)
|
|
697
707
|
|
|
698
708
|
```gleam
|
|
699
|
-
import
|
|
709
|
+
import mendraw/mendix/filter
|
|
700
710
|
|
|
701
711
|
// 비교 연산
|
|
702
712
|
filter.equals(filter.attribute("Status"), filter.literal("Active"))
|
|
@@ -719,12 +729,12 @@ filter.literal(value) // 상수 값
|
|
|
719
729
|
filter.empty() // null 비교용
|
|
720
730
|
```
|
|
721
731
|
|
|
722
|
-
### 8.11 날짜 (`
|
|
732
|
+
### 8.11 날짜 (`mendraw/mendix/date`)
|
|
723
733
|
|
|
724
734
|
> Gleam month는 1-based (1~12), JS는 0-based. glendix가 자동 변환합니다.
|
|
725
735
|
|
|
726
736
|
```gleam
|
|
727
|
-
import
|
|
737
|
+
import mendraw/mendix/date
|
|
728
738
|
|
|
729
739
|
date.now()
|
|
730
740
|
date.from_iso("2024-03-15T10:30:00Z")
|
|
@@ -741,45 +751,74 @@ date.to_input_value(d) // "2024-03-15" (input[type="date"]용)
|
|
|
741
751
|
date.from_input_value(s) // Option(JsDate)
|
|
742
752
|
```
|
|
743
753
|
|
|
744
|
-
### 8.12
|
|
754
|
+
### 8.12 Decimal (`mendraw/mendix/decimal`)
|
|
745
755
|
|
|
746
|
-
Big.js
|
|
756
|
+
Mendix Decimal 속성의 경계 변환 전용. Big.js 객체 ↔ Gleam 값 변환만 담당합니다.
|
|
757
|
+
산술/비교 연산이 필요하면 위젯 프로젝트에서 [dee](https://hexdocs.pm/dee/) 패키지를 추가하세요.
|
|
747
758
|
|
|
748
759
|
```gleam
|
|
749
|
-
import
|
|
760
|
+
import mendraw/mendix/decimal
|
|
761
|
+
|
|
762
|
+
// 생성 (Gleam → Mendix Big.js)
|
|
763
|
+
decimal.from_string("123.456")
|
|
764
|
+
decimal.from_int(100)
|
|
765
|
+
decimal.from_float(3.14) // 정밀도 손실 주의
|
|
766
|
+
|
|
767
|
+
// 변환 (Mendix Big.js → Gleam)
|
|
768
|
+
decimal.to_string(d) // "123.456"
|
|
769
|
+
decimal.to_float(d) // 123.456 (정밀도 손실 가능)
|
|
770
|
+
decimal.to_int(d) // 123 (소수점 이하 버림)
|
|
771
|
+
decimal.to_fixed(d, 2) // "123.46"
|
|
772
|
+
```
|
|
750
773
|
|
|
751
|
-
|
|
752
|
-
big.from_int(100)
|
|
774
|
+
**dee와 함께 사용하는 패턴:**
|
|
753
775
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
776
|
+
```gleam
|
|
777
|
+
import dee
|
|
778
|
+
import mendraw/mendix/decimal
|
|
779
|
+
|
|
780
|
+
// Mendix → dee (연산) → Mendix
|
|
781
|
+
let value = decimal.to_string(mendix_decimal) // Big.js → String
|
|
782
|
+
let result = dee.from_string(value) // String → dee
|
|
783
|
+
|> result.map(fn(d) { dee.add(d, dee.from_int(1)) })
|
|
784
|
+
|> result.map(dee.to_string) // dee → String
|
|
785
|
+
|> result.unwrap("0")
|
|
786
|
+
decimal.from_string(result) // String → Big.js
|
|
787
|
+
```
|
|
757
788
|
|
|
758
|
-
|
|
759
|
-
big.equal(a, b) // Bool
|
|
789
|
+
### 8.12.1 조건부 CSS 클래스 (`cx`)
|
|
760
790
|
|
|
761
|
-
|
|
762
|
-
|
|
791
|
+
`mendraw/mendix` 모듈의 `cx` 함수로 CSS 클래스를 조건부 조합합니다:
|
|
792
|
+
|
|
793
|
+
```gleam
|
|
794
|
+
import mendraw/mendix
|
|
795
|
+
|
|
796
|
+
mendix.cx([
|
|
797
|
+
#("widget-container", True),
|
|
798
|
+
#("active", is_active),
|
|
799
|
+
#("disabled", !is_editable),
|
|
800
|
+
])
|
|
801
|
+
// → "widget-container active" (is_active=True, is_editable=True일 때)
|
|
763
802
|
```
|
|
764
803
|
|
|
765
804
|
### 8.13 File, Icon, Formatter
|
|
766
805
|
|
|
767
806
|
```gleam
|
|
768
807
|
// FileValue / WebImage
|
|
769
|
-
import
|
|
808
|
+
import mendraw/mendix/file
|
|
770
809
|
file.uri(file_val) // String
|
|
771
810
|
file.name(file_val) // Option(String)
|
|
772
811
|
file.image_uri(img) // String
|
|
773
812
|
file.alt_text(img) // Option(String)
|
|
774
813
|
|
|
775
814
|
// WebIcon
|
|
776
|
-
import
|
|
815
|
+
import mendraw/mendix/icon
|
|
777
816
|
icon.icon_type(i) // Glyph | Image | IconFont
|
|
778
817
|
icon.icon_class(i) // String
|
|
779
818
|
icon.icon_url(i) // String
|
|
780
819
|
|
|
781
820
|
// ValueFormatter
|
|
782
|
-
import
|
|
821
|
+
import mendraw/mendix/formatter
|
|
783
822
|
formatter.format(fmt, Some(value)) // String
|
|
784
823
|
formatter.parse(fmt, "123.45") // Result(Option(a), Nil)
|
|
785
824
|
```
|
|
@@ -794,8 +833,8 @@ Studio Pro의 editorConfig 로직을 Gleam으로 작성합니다.
|
|
|
794
833
|
|
|
795
834
|
```gleam
|
|
796
835
|
import glendix/editor_config.{type Properties}
|
|
797
|
-
import
|
|
798
|
-
import
|
|
836
|
+
import mendraw/mendix
|
|
837
|
+
import mendraw/mendix.{type JsProps}
|
|
799
838
|
|
|
800
839
|
const bar_keys = "barWidth,barColor"
|
|
801
840
|
const line_keys = "lineStyle,lineCurve"
|
|
@@ -885,30 +924,93 @@ timer.clear_interval(id)
|
|
|
885
924
|
| 명령어 | 설명 |
|
|
886
925
|
|--------|------|
|
|
887
926
|
| `gleam build` | 컴파일 |
|
|
888
|
-
| `gleam run -m glendix/install` | 의존성 + 바인딩 + 위젯 .gleam 생성 |
|
|
927
|
+
| `gleam run -m glendix/install` | 의존성 + mendraw 위젯 다운로드 + 바인딩 + 위젯 .gleam 생성 |
|
|
889
928
|
| `gleam run -m glendix/dev` | 개발 서버 (HMR) |
|
|
890
929
|
| `gleam run -m glendix/build` | 프로덕션 빌드 (.mpk) |
|
|
891
930
|
| `gleam run -m glendix/start` | Mendix 테스트 프로젝트 연동 |
|
|
892
931
|
| `gleam run -m glendix/release` | 릴리즈 빌드 |
|
|
893
932
|
| `gleam run -m glendix/lint` | ESLint 검사 |
|
|
894
933
|
| `gleam run -m glendix/lint_fix` | ESLint 자동 수정 |
|
|
895
|
-
| `gleam run -m
|
|
934
|
+
| `gleam run -m mendraw/marketplace` | Marketplace 위젯 다운로드 (인터랙티브, gleam.toml 자동 기록) |
|
|
896
935
|
| `gleam run -m glendix/define` | 위젯 프로퍼티 정의 TUI 에디터 |
|
|
897
936
|
|
|
898
|
-
**PM
|
|
937
|
+
**PM 감지:** `gleam.toml`의 `[tools.glendix] pm = "pnpm"` 오버라이드 우선. 없으면 lock 파일 기반: `pnpm-lock.yaml` → pnpm / `bun.lockb`·`bun.lock` → bun / 기본값 → npm
|
|
938
|
+
|
|
939
|
+
---
|
|
940
|
+
|
|
941
|
+
## 12. 멀티 위젯 컴포넌트
|
|
942
|
+
|
|
943
|
+
하나의 `.mpk` 파일에 여러 위젯 컴포넌트를 묶어서 개발할 수 있습니다 (예: Charts.mpk).
|
|
944
|
+
|
|
945
|
+
### 12.1 설정
|
|
946
|
+
|
|
947
|
+
`package.json`에 `widgets` 맵을 추가합니다:
|
|
948
|
+
|
|
949
|
+
```json
|
|
950
|
+
{
|
|
951
|
+
"widgetName": "AreaChart",
|
|
952
|
+
"widgets": {
|
|
953
|
+
"AreaChart": "area_chart",
|
|
954
|
+
"BarChart": "bar_chart",
|
|
955
|
+
"PieChart": "pie_chart"
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
- `widgetName`: 기본 위젯 이름 (`pluggable-widgets-tools` 진입점)
|
|
961
|
+
- `widgets`: 컴포넌트명(PascalCase) → Gleam 함수명(snake_case) 맵
|
|
962
|
+
- `widgets` 미지정 시 기존 단일 위젯 동작 유지 (하위 호환)
|
|
963
|
+
|
|
964
|
+
### 12.2 Gleam 코드
|
|
965
|
+
|
|
966
|
+
메인 모듈에서 모든 위젯 함수를 export합니다:
|
|
967
|
+
|
|
968
|
+
```gleam
|
|
969
|
+
import mendraw/mendix.{type JsProps}
|
|
970
|
+
import redraw.{type Element}
|
|
971
|
+
|
|
972
|
+
pub fn area_chart(props: JsProps) -> Element { ... }
|
|
973
|
+
pub fn bar_chart(props: JsProps) -> Element { ... }
|
|
974
|
+
pub fn pie_chart(props: JsProps) -> Element { ... }
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
### 12.3 CSS 전략
|
|
978
|
+
|
|
979
|
+
위젯별 CSS 파일이 있으면 우선 사용, 없으면 공유 CSS 폴백:
|
|
980
|
+
|
|
981
|
+
1. `src/ui/{ComponentName}.css` 존재 → 해당 파일 import
|
|
982
|
+
2. 미존재 + `src/ui/{widgetName}.css` 존재 → 공유 CSS import
|
|
983
|
+
3. 둘 다 미존재 → CSS import 생략
|
|
984
|
+
|
|
985
|
+
### 12.4 Editor Config / Preview
|
|
986
|
+
|
|
987
|
+
위젯별 파일 → 공유 파일 폴백:
|
|
988
|
+
|
|
989
|
+
1. `src/{snake_fn}_editor_config.gleam` 존재 → 위젯별 editor config
|
|
990
|
+
2. 미존재 + `src/editor_config.gleam` 존재 → 공유 editor config
|
|
991
|
+
3. Preview도 동일 패턴 (`{snake_fn}_editor_preview.gleam`)
|
|
992
|
+
|
|
993
|
+
### 12.5 빌드
|
|
994
|
+
|
|
995
|
+
`gleam run -m glendix/build` 실행 시 자동으로:
|
|
996
|
+
|
|
997
|
+
- 각 위젯별 브릿지 파일 (`src/{ComponentName}.js`) 생성
|
|
998
|
+
- 기본 위젯은 `pluggable-widgets-tools`가 처리
|
|
999
|
+
- 추가 위젯은 rollup config에서 엔트리 자동 추가
|
|
1000
|
+
- 각 위젯의 XML에서 `id`를 읽어 출력 경로 결정
|
|
899
1001
|
|
|
900
1002
|
---
|
|
901
1003
|
|
|
902
|
-
##
|
|
1004
|
+
## 13. 실전 패턴
|
|
903
1005
|
|
|
904
1006
|
### 12.1 폼 입력 위젯
|
|
905
1007
|
|
|
906
1008
|
```gleam
|
|
907
1009
|
import gleam/option.{None, Some}
|
|
908
|
-
import
|
|
909
|
-
import
|
|
910
|
-
import
|
|
911
|
-
import
|
|
1010
|
+
import mendraw/mendix
|
|
1011
|
+
import mendraw/mendix.{type JsProps}
|
|
1012
|
+
import mendraw/mendix/action
|
|
1013
|
+
import mendraw/mendix/editable_value as ev
|
|
912
1014
|
import redraw.{type Element}
|
|
913
1015
|
import redraw/dom/attribute
|
|
914
1016
|
import redraw/dom/events
|
|
@@ -957,11 +1059,11 @@ pub fn text_input_widget(props: JsProps) -> Element {
|
|
|
957
1059
|
```gleam
|
|
958
1060
|
import gleam/list
|
|
959
1061
|
import gleam/option.{None, Some}
|
|
960
|
-
import
|
|
961
|
-
import
|
|
962
|
-
import
|
|
963
|
-
import
|
|
964
|
-
import
|
|
1062
|
+
import mendraw/mendix
|
|
1063
|
+
import mendraw/mendix.{type JsProps}
|
|
1064
|
+
import mendraw/mendix/editable_value as ev
|
|
1065
|
+
import mendraw/mendix/list_attribute as la
|
|
1066
|
+
import mendraw/mendix/list_value as lv
|
|
965
1067
|
import redraw.{type Element}
|
|
966
1068
|
import redraw/dom/attribute
|
|
967
1069
|
import redraw/dom/html
|
|
@@ -990,10 +1092,10 @@ pub fn data_table(props: JsProps) -> Element {
|
|
|
990
1092
|
|
|
991
1093
|
```gleam
|
|
992
1094
|
import gleam/option.{None, Some}
|
|
993
|
-
import
|
|
994
|
-
import
|
|
995
|
-
import
|
|
996
|
-
import
|
|
1095
|
+
import mendraw/mendix
|
|
1096
|
+
import mendraw/mendix.{type JsProps}
|
|
1097
|
+
import mendraw/mendix/filter
|
|
1098
|
+
import mendraw/mendix/list_value as lv
|
|
997
1099
|
import redraw.{type Element}
|
|
998
1100
|
import redraw/dom/attribute
|
|
999
1101
|
import redraw/dom/events
|
|
@@ -1028,7 +1130,7 @@ pub fn searchable_list(props: JsProps) -> Element {
|
|
|
1028
1130
|
|
|
1029
1131
|
---
|
|
1030
1132
|
|
|
1031
|
-
##
|
|
1133
|
+
## 14. 절대 하지 말 것
|
|
1032
1134
|
|
|
1033
1135
|
| 실수 | 올바른 방법 |
|
|
1034
1136
|
|------|------------|
|
|
@@ -1037,15 +1139,15 @@ pub fn searchable_list(props: JsProps) -> Element {
|
|
|
1037
1139
|
| 조건 안에서 Hook 호출 | Hook은 항상 함수 최상위에서 호출 |
|
|
1038
1140
|
| `html.text("")`로 빈 렌더링 | `html.none()` 사용 |
|
|
1039
1141
|
| `binding.resolve(m(), "pie_chart")` | JS 원본 이름 유지: `"PieChart"` |
|
|
1040
|
-
| 외부 React 컴포넌트용 `.mjs` 직접 작성 | `bindings
|
|
1041
|
-
| `.mpk` 위젯용 `.mjs` 직접 작성 | `widgets
|
|
1142
|
+
| 외부 React 컴포넌트용 `.mjs` 직접 작성 | `gleam.toml [tools.glendix.bindings]` + `glendix/binding` 사용 |
|
|
1143
|
+
| `.mpk` 위젯용 `.mjs` 직접 작성 | `gleam.toml [tools.mendraw.widgets.*]` + `mendraw/widget` 사용 |
|
|
1042
1144
|
| `date.month()`에 0-based 값 전달 | glendix가 1↔0 자동 변환 |
|
|
1043
1145
|
| Editor config에서 Gleam List 사용 | 콤마 구분 String 사용 (Jint 호환) |
|
|
1044
1146
|
| FFI `.mjs`에 비즈니스 로직 | `.gleam`에 작성. `.mjs`는 JS 런타임 접근만 |
|
|
1045
1147
|
|
|
1046
1148
|
---
|
|
1047
1149
|
|
|
1048
|
-
##
|
|
1150
|
+
## 15. 트러블슈팅
|
|
1049
1151
|
|
|
1050
1152
|
| 문제 | 원인 | 해결 |
|
|
1051
1153
|
|------|------|------|
|
|
@@ -1053,9 +1155,8 @@ pub fn searchable_list(props: JsProps) -> Element {
|
|
|
1053
1155
|
| `Cannot read property of undefined` | 없는 prop 접근 | `get_prop` (Option) 사용, prop 이름 확인 |
|
|
1054
1156
|
| Hook 순서 에러 | 조건부 Hook 호출 | 항상 동일 순서로 호출 (React Rules) |
|
|
1055
1157
|
| 바인딩 미생성 | `binding_ffi.mjs` 스텁 상태 | `gleam run -m glendix/install` |
|
|
1056
|
-
| 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `widgets
|
|
1158
|
+
| 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `gleam.toml [tools.mendraw.widgets.*]` 설정 후 install |
|
|
1057
1159
|
| `could not be resolved` | npm 패키지 미설치 | `npm install <패키지명>` |
|
|
1058
1160
|
| `.env` PAT 오류 | marketplace 인증 실패 | [Developer Settings](https://user-settings.mendix.com/link/developersettings)에서 PAT 재발급 |
|
|
1059
|
-
| Playwright 오류 | chromium 미설치 | `npx playwright install chromium` |
|
|
1060
1161
|
|
|
1061
1162
|
---
|