create-mendix-widget-gleam 2.0.19 → 3.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 +1 -1
- package/package.json +1 -1
- package/src/i18n.mjs +394 -334
- package/src/index.mjs +284 -231
- package/src/licenses.mjs +147 -0
- package/src/prompts.mjs +247 -142
- package/src/scaffold.mjs +108 -97
- package/src/templates/claude_md.mjs +7 -6
- package/src/templates/readme_md.mjs +88 -76
- package/src/templates/widgets_readme.mjs +18 -15
- package/template/docs/glendix_guide.md +1050 -2538
- package/template/gleam.toml +5 -2
- package/template/package.json +10 -7
- package/template/src/__WidgetName__.xml +1 -1
- package/template/src/__widget_name__.gleam +3 -3
- package/template/src/components/hello_world.gleam +5 -5
- package/template/src/editor_config.gleam +4 -8
- package/template/src/editor_preview.gleam +3 -3
- package/template/src/package.xml +2 -2
- package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@command.cache +0 -0
- package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@cursor.cache +0 -0
- package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@cursor.cache_meta +0 -0
- package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@event.cache +0 -0
- package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@internal@consts.cache +0 -0
- package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@stdout.cache +0 -0
- package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@style.cache +0 -0
- package/tui/build/dev/javascript/etch/_gleam_artefacts/etch@terminal.cache +0 -0
- package/tui/build/dev/javascript/etch/etch/event.mjs +36 -30
- package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@application.cache +0 -0
- package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@atom.cache +0 -0
- package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@charlist.cache +0 -0
- package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@node.cache +0 -0
- package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@port.cache +0 -0
- package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@process.cache +0 -0
- package/tui/build/dev/javascript/gleam_erlang/_gleam_artefacts/gleam@erlang@reference.cache +0 -0
- package/tui/build/dev/javascript/gleam_javascript/_gleam_artefacts/gleam@javascript@array.cache +0 -0
- package/tui/build/dev/javascript/gleam_javascript/_gleam_artefacts/gleam@javascript@promise.cache +0 -0
- package/tui/build/dev/javascript/gleam_javascript/_gleam_artefacts/gleam@javascript@symbol.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@bit_array.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@bool.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@bool.cache_inline +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@bytes_tree.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@dict.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@dynamic.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@dynamic@decode.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@dynamic@decode.cache_meta +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@float.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@function.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@int.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@io.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@list.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@option.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@order.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@pair.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@result.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@result.cache_inline +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@set.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@string.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@string.cache_meta +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@string_tree.cache +0 -0
- package/tui/build/dev/javascript/gleam_stdlib/_gleam_artefacts/gleam@uri.cache +0 -0
- package/tui/build/dev/javascript/gleam_version +1 -1
- package/tui/build/dev/javascript/prelude.mjs +10 -26
- package/tui/build/dev/javascript/tui/_gleam_artefacts/tui.cache +0 -0
- package/tui/build/dev/javascript/tui/_gleam_artefacts/tui.cache_meta +0 -0
- package/tui/build/dev/javascript/tui/_gleam_artefacts/tui@prompt.cache +0 -0
- package/tui/build/dev/javascript/tui/_gleam_artefacts/tui@prompt.cache_meta +0 -0
- package/tui/build/dev/javascript/tui/tui/prompt.mjs +713 -52
- package/tui/build/dev/javascript/tui/tui.mjs +438 -51
- package/tui/build/dev/javascript/tui/tui_ffi.mjs +12 -0
- package/template/LICENSE +0 -15
package/src/scaffold.mjs
CHANGED
|
@@ -1,97 +1,108 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 파일 복사 + 템플릿 치환
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { readdir, readFile, writeFile, mkdir, copyFile } from "node:fs/promises";
|
|
6
|
-
import { join, relative } from "node:path";
|
|
7
|
-
|
|
8
|
-
/** 바이너리 판별용 확장자 */
|
|
9
|
-
const BINARY_EXTS = new Set([".png", ".jpg", ".gif", ".ico", ".woff", ".woff2", ".ttf", ".eot"]);
|
|
10
|
-
|
|
11
|
-
/** 재귀적으로 디렉토리 내 모든 파일 경로를 수집 */
|
|
12
|
-
async function walkDir(dir) {
|
|
13
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
14
|
-
const files = [];
|
|
15
|
-
for (const entry of entries) {
|
|
16
|
-
const fullPath = join(dir, entry.name);
|
|
17
|
-
if (entry.isDirectory()) {
|
|
18
|
-
files.push(...(await walkDir(fullPath)));
|
|
19
|
-
} else {
|
|
20
|
-
files.push(fullPath);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return files;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// npm publish 시 제외되는 dotfile을 언더스코어 접두사로 보관하고 복원
|
|
27
|
-
const DOTFILE_MAP = { _gitignore: ".gitignore" };
|
|
28
|
-
|
|
29
|
-
/** 파일명에서 플레이스홀더 치환 + dotfile 복원 */
|
|
30
|
-
function replaceFileName(name, names) {
|
|
31
|
-
if (DOTFILE_MAP[name]) return DOTFILE_MAP[name];
|
|
32
|
-
return name
|
|
33
|
-
.replace(/__WidgetName__/g, names.pascalCase)
|
|
34
|
-
.replace(/__widget_name__/g, names.snakeCase);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** 파일 내용에서 플레이스홀더 치환 */
|
|
38
|
-
function replaceContent(content, names, pmConfig, templateComments) {
|
|
39
|
-
let result = content
|
|
40
|
-
.replace(/\{\{PASCAL_CASE\}\}/g, names.pascalCase)
|
|
41
|
-
.replace(/\{\{SNAKE_CASE\}\}/g, names.snakeCase)
|
|
42
|
-
.replace(/\{\{LOWERCASE\}\}/g, names.lowerCase)
|
|
43
|
-
.replace(/\{\{DISPLAY_NAME\}\}/g, names.displayName)
|
|
44
|
-
.replace(/\{\{KEBAB_CASE\}\}/g, names.kebabCase);
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
result = result
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 파일 복사 + 템플릿 치환
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readdir, readFile, writeFile, mkdir, copyFile } from "node:fs/promises";
|
|
6
|
+
import { join, relative } from "node:path";
|
|
7
|
+
|
|
8
|
+
/** 바이너리 판별용 확장자 */
|
|
9
|
+
const BINARY_EXTS = new Set([".png", ".jpg", ".gif", ".ico", ".woff", ".woff2", ".ttf", ".eot"]);
|
|
10
|
+
|
|
11
|
+
/** 재귀적으로 디렉토리 내 모든 파일 경로를 수집 */
|
|
12
|
+
async function walkDir(dir) {
|
|
13
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
14
|
+
const files = [];
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
files.push(...(await walkDir(fullPath)));
|
|
19
|
+
} else {
|
|
20
|
+
files.push(fullPath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// npm publish 시 제외되는 dotfile을 언더스코어 접두사로 보관하고 복원
|
|
27
|
+
const DOTFILE_MAP = { _gitignore: ".gitignore" };
|
|
28
|
+
|
|
29
|
+
/** 파일명에서 플레이스홀더 치환 + dotfile 복원 */
|
|
30
|
+
function replaceFileName(name, names) {
|
|
31
|
+
if (DOTFILE_MAP[name]) return DOTFILE_MAP[name];
|
|
32
|
+
return name
|
|
33
|
+
.replace(/__WidgetName__/g, names.pascalCase)
|
|
34
|
+
.replace(/__widget_name__/g, names.snakeCase);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 파일 내용에서 플레이스홀더 치환 */
|
|
38
|
+
function replaceContent(content, names, pmConfig, templateComments, options) {
|
|
39
|
+
let result = content
|
|
40
|
+
.replace(/\{\{PASCAL_CASE\}\}/g, names.pascalCase)
|
|
41
|
+
.replace(/\{\{SNAKE_CASE\}\}/g, names.snakeCase)
|
|
42
|
+
.replace(/\{\{LOWERCASE\}\}/g, names.lowerCase)
|
|
43
|
+
.replace(/\{\{DISPLAY_NAME\}\}/g, names.displayName)
|
|
44
|
+
.replace(/\{\{KEBAB_CASE\}\}/g, names.kebabCase);
|
|
45
|
+
|
|
46
|
+
if (options) {
|
|
47
|
+
result = result
|
|
48
|
+
.replace(/\{\{ORGANIZATION\}\}/g, options.organization)
|
|
49
|
+
.replace(/\{\{COPYRIGHT\}\}/g, options.copyright)
|
|
50
|
+
.replace(/\{\{LICENSE_ID\}\}/g, options.license)
|
|
51
|
+
.replace(/\{\{VERSION\}\}/g, options.version)
|
|
52
|
+
.replace(/\{\{AUTHOR\}\}/g, options.author)
|
|
53
|
+
.replace(/\{\{PROJECT_PATH\}\}/g, options.projectPath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (templateComments) {
|
|
57
|
+
result = result.replace(/\{\{I18N:(\w+)\}\}/g, (_, key) => {
|
|
58
|
+
return templateComments[key] ?? `{{I18N:${key}}}`;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 템플릿을 대상 디렉토리에 스케폴딩
|
|
67
|
+
* @param {string} templateDir - 템플릿 디렉토리 경로
|
|
68
|
+
* @param {string} targetDir - 생성할 프로젝트 디렉토리 경로
|
|
69
|
+
* @param {object} names - 이름 변환 결과
|
|
70
|
+
* @param {object} pmConfig - 패키지 매니저 설정
|
|
71
|
+
* @param {object} [templateComments] - i18n 템플릿 주석 ({{I18N:*}} 치환용)
|
|
72
|
+
* @param {object} [options] - 추가 옵션 (organization, copyright, license, version, author, projectPath)
|
|
73
|
+
*/
|
|
74
|
+
export async function scaffold(templateDir, targetDir, names, pmConfig, templateComments, options) {
|
|
75
|
+
const files = await walkDir(templateDir);
|
|
76
|
+
const created = [];
|
|
77
|
+
|
|
78
|
+
for (const srcPath of files) {
|
|
79
|
+
// 템플릿 기준 상대 경로
|
|
80
|
+
const relPath = relative(templateDir, srcPath);
|
|
81
|
+
|
|
82
|
+
// 경로의 각 부분에서 파일명 치환
|
|
83
|
+
const destRelPath = relPath
|
|
84
|
+
.split(/[\\/]/)
|
|
85
|
+
.map((part) => replaceFileName(part, names))
|
|
86
|
+
.join("/");
|
|
87
|
+
|
|
88
|
+
const destPath = join(targetDir, destRelPath);
|
|
89
|
+
const ext = srcPath.substring(srcPath.lastIndexOf(".")).toLowerCase();
|
|
90
|
+
|
|
91
|
+
// 디렉토리 생성
|
|
92
|
+
await mkdir(join(destPath, ".."), { recursive: true });
|
|
93
|
+
|
|
94
|
+
if (BINARY_EXTS.has(ext)) {
|
|
95
|
+
// 바이너리 파일은 그대로 복사
|
|
96
|
+
await copyFile(srcPath, destPath);
|
|
97
|
+
} else {
|
|
98
|
+
// 텍스트 파일은 내용 치환
|
|
99
|
+
const content = await readFile(srcPath, "utf-8");
|
|
100
|
+
const replaced = replaceContent(content, names, pmConfig, templateComments, options);
|
|
101
|
+
await writeFile(destPath, replaced, "utf-8");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
created.push(destRelPath);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return created;
|
|
108
|
+
}
|
|
@@ -9,7 +9,7 @@ const COMMENT_LANG_INSTRUCTIONS = {
|
|
|
9
9
|
ja: "Use Japanese comments",
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export function generateClaudeMdContent(lang, names, pm, pmConfig) {
|
|
12
|
+
export function generateClaudeMdContent(lang, names, pm, pmConfig, organization) {
|
|
13
13
|
const commentInstruction =
|
|
14
14
|
COMMENT_LANG_INSTRUCTIONS[lang] ?? COMMENT_LANG_INSTRUCTIONS["en"];
|
|
15
15
|
|
|
@@ -41,7 +41,7 @@ IMPORTANT: Breaking these rules will break the build or compromise the architect
|
|
|
41
41
|
- **Do not write JSX/JS files directly.** All widget logic and UI must be written in Gleam
|
|
42
42
|
- **Do not write FFI files (.mjs) in the widget project.** React/Mendix FFI is provided by the glendix package
|
|
43
43
|
- **Do not manually manage bridge JS files (src/*.js).** glendix auto-generates/deletes them at build time
|
|
44
|
-
- **
|
|
44
|
+
- **React bindings use \`redraw\`/\`redraw_dom\` packages.** glendix v3.0 no longer provides React bindings directly
|
|
45
45
|
- The Gleam compilation output path (\`build/dev/javascript/{gleam.toml name}/\`) must match the Rollup input path
|
|
46
46
|
- Mendix widget names allow only alphabetic characters (a-zA-Z)
|
|
47
47
|
|
|
@@ -53,7 +53,7 @@ IMPORTANT: Breaking these rules will break the build or compromise the architect
|
|
|
53
53
|
|
|
54
54
|
## Architecture
|
|
55
55
|
|
|
56
|
-
Widget entry point signature: \`pub fn widget(props: JsProps) ->
|
|
56
|
+
Widget entry point signature: \`pub fn widget(props: JsProps) -> Element\` — identical to a React functional component. \`JsProps\` from \`glendix/mendix\`, \`Element\` from \`redraw\`.
|
|
57
57
|
|
|
58
58
|
- \`src/${names.snakeCase}.gleam\` — Main widget (called by Mendix runtime)
|
|
59
59
|
- \`src/editor_config.gleam\` — Studio Pro property panel configuration
|
|
@@ -72,8 +72,8 @@ src/*.gleam → gleam build → build/dev/javascript/**/*.mjs → Bridge JS (aut
|
|
|
72
72
|
|
|
73
73
|
## Mendix Widget Conventions
|
|
74
74
|
|
|
75
|
-
- Widget ID:
|
|
76
|
-
- \`packagePath: "
|
|
75
|
+
- Widget ID: \`${organization}.${names.lowerCase}.${names.pascalCase}\`
|
|
76
|
+
- \`packagePath: "${organization}"\` in \`package.json\` determines the deployment path
|
|
77
77
|
- \`needsEntityContext="true"\` → Requires Mendix data context
|
|
78
78
|
- \`offlineCapable="true"\` → Offline support
|
|
79
79
|
- \`.mpk\` output: \`dist/\` directory
|
|
@@ -84,7 +84,8 @@ src/*.gleam → gleam build → build/dev/javascript/**/*.mjs → Bridge JS (aut
|
|
|
84
84
|
- Mendix props (\`JsProps\`) are accessed via \`mendix.get_prop\`/\`mendix.get_string_prop\` etc.
|
|
85
85
|
- Mendix complex types (\`EditableValue\`, \`ActionValue\`, \`ListValue\`) are opaque types with FFI accessors
|
|
86
86
|
- JS \`undefined\` ↔ Gleam \`Option\` conversion is handled automatically at the FFI boundary
|
|
87
|
-
- HTML
|
|
87
|
+
- HTML elements use \`redraw/dom/html\`, attributes use \`redraw/dom/attribute\`, events use \`redraw/dom/events\`
|
|
88
|
+
- lustre TEA pattern is supported via \`glendix/lustre\` bridge (optional)
|
|
88
89
|
- Gleam tuples \`#(a, b)\` = JS \`[a, b]\` — directly compatible with \`useState\` return values
|
|
89
90
|
|
|
90
91
|
## Reference Docs
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* README.md template — 3 language versions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export function generateReadmeContent(lang, names, pm, pmConfig) {
|
|
5
|
+
export function generateReadmeContent(lang, names, pm, pmConfig, license) {
|
|
6
6
|
const installCmd =
|
|
7
7
|
pm === "npm"
|
|
8
8
|
? "npm install"
|
|
@@ -14,11 +14,11 @@ export function generateReadmeContent(lang, names, pm, pmConfig) {
|
|
|
14
14
|
|
|
15
15
|
switch (lang) {
|
|
16
16
|
case "ko":
|
|
17
|
-
return generateKo(names, pm, installCmd);
|
|
17
|
+
return generateKo(names, pm, installCmd, license);
|
|
18
18
|
case "ja":
|
|
19
|
-
return generateJa(names, pm, installCmd);
|
|
19
|
+
return generateJa(names, pm, installCmd, license);
|
|
20
20
|
default:
|
|
21
|
-
return generateEn(names, pm, installCmd);
|
|
21
|
+
return generateEn(names, pm, installCmd, license);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -26,26 +26,26 @@ export function generateReadmeContent(lang, names, pm, pmConfig) {
|
|
|
26
26
|
// English
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
|
|
29
|
-
function generateEn(names, pm, installCmd) {
|
|
29
|
+
function generateEn(names, pm, installCmd, license) {
|
|
30
30
|
return `# ${names.pascalCase}
|
|
31
31
|
|
|
32
32
|
A Mendix Pluggable Widget written in Gleam.
|
|
33
33
|
|
|
34
34
|
## Core Principles
|
|
35
35
|
|
|
36
|
-
The Gleam function \`fn(JsProps) ->
|
|
36
|
+
The Gleam function \`fn(JsProps) -> Element\` has the same signature as a React functional component. React bindings come from the \`redraw\`/\`redraw_dom\` packages, while glendix handles Mendix API access and JS interop, so widget projects only need to focus on business logic.
|
|
37
37
|
|
|
38
38
|
\`\`\`gleam
|
|
39
39
|
// src/${names.snakeCase}.gleam
|
|
40
|
-
import glendix/mendix
|
|
41
|
-
import
|
|
42
|
-
import
|
|
43
|
-
import
|
|
40
|
+
import glendix/mendix.{type JsProps}
|
|
41
|
+
import redraw.{type Element}
|
|
42
|
+
import redraw/dom/attribute
|
|
43
|
+
import redraw/dom/html
|
|
44
44
|
|
|
45
|
-
pub fn widget(props: JsProps) ->
|
|
45
|
+
pub fn widget(props: JsProps) -> Element {
|
|
46
46
|
let sample_text = mendix.get_string_prop(props, "sampleText")
|
|
47
47
|
html.div([attribute.class("widget-hello-world")], [
|
|
48
|
-
|
|
48
|
+
html.text("Hello " <> sample_text),
|
|
49
49
|
])
|
|
50
50
|
}
|
|
51
51
|
\`\`\`
|
|
@@ -53,11 +53,12 @@ pub fn widget(props: JsProps) -> ReactElement {
|
|
|
53
53
|
Mendix complex types can also be used type-safely from Gleam:
|
|
54
54
|
|
|
55
55
|
\`\`\`gleam
|
|
56
|
-
import glendix/mendix
|
|
56
|
+
import glendix/mendix.{type JsProps}
|
|
57
57
|
import glendix/mendix/editable_value
|
|
58
58
|
import glendix/mendix/action
|
|
59
|
+
import redraw.{type Element}
|
|
59
60
|
|
|
60
|
-
pub fn widget(props: JsProps) ->
|
|
61
|
+
pub fn widget(props: JsProps) -> Element {
|
|
61
62
|
// Access EditableValue
|
|
62
63
|
let name_attr: EditableValue = mendix.get_prop_required(props, "name")
|
|
63
64
|
let display = editable_value.display_value(name_attr)
|
|
@@ -126,7 +127,7 @@ bindings.json # External React component binding configurat
|
|
|
126
127
|
package.json # npm dependencies (React, external libraries, etc.)
|
|
127
128
|
\`\`\`
|
|
128
129
|
|
|
129
|
-
React/Mendix
|
|
130
|
+
React bindings come from [redraw](https://hexdocs.pm/redraw/)/[redraw_dom](https://hexdocs.pm/redraw_dom/), while Mendix API and JS Interop bindings are provided by [glendix](https://hexdocs.pm/glendix/).
|
|
130
131
|
|
|
131
132
|
## Using External React Components
|
|
132
133
|
|
|
@@ -162,17 +163,18 @@ gleam run -m glendix/install
|
|
|
162
163
|
|
|
163
164
|
\`\`\`gleam
|
|
164
165
|
import glendix/binding
|
|
165
|
-
import glendix/
|
|
166
|
-
import
|
|
166
|
+
import glendix/interop
|
|
167
|
+
import redraw.{type Element}
|
|
168
|
+
import redraw/dom/attribute.{type Attribute}
|
|
167
169
|
|
|
168
170
|
fn m() { binding.module("recharts") }
|
|
169
171
|
|
|
170
|
-
pub fn pie_chart(attrs: List(Attribute), children: List(
|
|
171
|
-
|
|
172
|
+
pub fn pie_chart(attrs: List(Attribute), children: List(Element)) -> Element {
|
|
173
|
+
interop.component_el(binding.resolve(m(), "PieChart"), attrs, children)
|
|
172
174
|
}
|
|
173
175
|
|
|
174
|
-
pub fn tooltip(attrs: List(Attribute)) ->
|
|
175
|
-
|
|
176
|
+
pub fn tooltip(attrs: List(Attribute)) -> Element {
|
|
177
|
+
interop.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
|
|
176
178
|
}
|
|
177
179
|
\`\`\`
|
|
178
180
|
|
|
@@ -229,18 +231,19 @@ This automatically:
|
|
|
229
231
|
|
|
230
232
|
\`\`\`gleam
|
|
231
233
|
// src/widgets/switch.gleam (auto-generated)
|
|
232
|
-
import glendix/mendix
|
|
233
|
-
import glendix/
|
|
234
|
-
import
|
|
234
|
+
import glendix/mendix.{type JsProps}
|
|
235
|
+
import glendix/interop
|
|
236
|
+
import redraw.{type Element}
|
|
237
|
+
import redraw/dom/attribute
|
|
235
238
|
import glendix/widget
|
|
236
239
|
|
|
237
240
|
/// Render Switch widget - reads properties from props and passes them to the widget
|
|
238
|
-
pub fn render(props: JsProps) ->
|
|
241
|
+
pub fn render(props: JsProps) -> Element {
|
|
239
242
|
let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
|
|
240
243
|
let action = mendix.get_prop_required(props, "action")
|
|
241
244
|
|
|
242
245
|
let comp = widget.component("Switch")
|
|
243
|
-
|
|
246
|
+
interop.component_el(
|
|
244
247
|
comp,
|
|
245
248
|
[
|
|
246
249
|
attribute.attribute("booleanAttribute", boolean_attribute),
|
|
@@ -267,13 +270,14 @@ Widget names use the \`<name>\` value from the \`.mpk\`'s internal XML, and prop
|
|
|
267
270
|
## Tech Stack
|
|
268
271
|
|
|
269
272
|
- **Gleam** → JavaScript compilation
|
|
270
|
-
- **[glendix](https://hexdocs.pm/glendix/)** —
|
|
273
|
+
- **[glendix](https://hexdocs.pm/glendix/)** — Mendix API + JS Interop Gleam bindings
|
|
274
|
+
- **[redraw](https://hexdocs.pm/redraw/)** / **[redraw_dom](https://hexdocs.pm/redraw_dom/)** — React Gleam bindings
|
|
271
275
|
- **Mendix Pluggable Widget** (React 19)
|
|
272
276
|
- **${pm}** — Package manager
|
|
273
277
|
|
|
274
278
|
## License
|
|
275
279
|
|
|
276
|
-
|
|
280
|
+
${license}
|
|
277
281
|
`;
|
|
278
282
|
}
|
|
279
283
|
|
|
@@ -281,26 +285,26 @@ Apache-2.0
|
|
|
281
285
|
// Korean
|
|
282
286
|
// ---------------------------------------------------------------------------
|
|
283
287
|
|
|
284
|
-
function generateKo(names, pm, installCmd) {
|
|
288
|
+
function generateKo(names, pm, installCmd, license) {
|
|
285
289
|
return `# ${names.pascalCase}
|
|
286
290
|
|
|
287
291
|
Gleam 언어로 작성된 Mendix Pluggable Widget.
|
|
288
292
|
|
|
289
293
|
## 핵심 원리
|
|
290
294
|
|
|
291
|
-
Gleam 함수 \`fn(JsProps) ->
|
|
295
|
+
Gleam 함수 \`fn(JsProps) -> Element\`는 React 함수형 컴포넌트와 동일한 시그니처다. React 바인딩은 \`redraw\`/\`redraw_dom\` 패키지가, Mendix API 접근과 JS interop은 glendix가 제공하므로, 위젯 프로젝트에서는 비즈니스 로직에만 집중하면 된다.
|
|
292
296
|
|
|
293
297
|
\`\`\`gleam
|
|
294
298
|
// src/${names.snakeCase}.gleam
|
|
295
|
-
import glendix/mendix
|
|
296
|
-
import
|
|
297
|
-
import
|
|
298
|
-
import
|
|
299
|
+
import glendix/mendix.{type JsProps}
|
|
300
|
+
import redraw.{type Element}
|
|
301
|
+
import redraw/dom/attribute
|
|
302
|
+
import redraw/dom/html
|
|
299
303
|
|
|
300
|
-
pub fn widget(props: JsProps) ->
|
|
304
|
+
pub fn widget(props: JsProps) -> Element {
|
|
301
305
|
let sample_text = mendix.get_string_prop(props, "sampleText")
|
|
302
306
|
html.div([attribute.class("widget-hello-world")], [
|
|
303
|
-
|
|
307
|
+
html.text("Hello " <> sample_text),
|
|
304
308
|
])
|
|
305
309
|
}
|
|
306
310
|
\`\`\`
|
|
@@ -308,11 +312,12 @@ pub fn widget(props: JsProps) -> ReactElement {
|
|
|
308
312
|
Mendix 복합 타입도 Gleam에서 타입 안전하게 사용할 수 있다:
|
|
309
313
|
|
|
310
314
|
\`\`\`gleam
|
|
311
|
-
import glendix/mendix
|
|
315
|
+
import glendix/mendix.{type JsProps}
|
|
312
316
|
import glendix/mendix/editable_value
|
|
313
317
|
import glendix/mendix/action
|
|
318
|
+
import redraw.{type Element}
|
|
314
319
|
|
|
315
|
-
pub fn widget(props: JsProps) ->
|
|
320
|
+
pub fn widget(props: JsProps) -> Element {
|
|
316
321
|
// EditableValue 접근
|
|
317
322
|
let name_attr: EditableValue = mendix.get_prop_required(props, "name")
|
|
318
323
|
let display = editable_value.display_value(name_attr)
|
|
@@ -381,7 +386,7 @@ bindings.json # 외부 React 컴포넌트 바인딩 설정
|
|
|
381
386
|
package.json # npm 의존성 (React, 외부 라이브러리 등)
|
|
382
387
|
\`\`\`
|
|
383
388
|
|
|
384
|
-
React/Mendix
|
|
389
|
+
React 바인딩은 [redraw](https://hexdocs.pm/redraw/)/[redraw_dom](https://hexdocs.pm/redraw_dom/)이, Mendix API 및 JS Interop 바인딩은 [glendix](https://hexdocs.pm/glendix/)가 제공합니다.
|
|
385
390
|
|
|
386
391
|
## 외부 React 컴포넌트 사용
|
|
387
392
|
|
|
@@ -417,17 +422,18 @@ gleam run -m glendix/install
|
|
|
417
422
|
|
|
418
423
|
\`\`\`gleam
|
|
419
424
|
import glendix/binding
|
|
420
|
-
import glendix/
|
|
421
|
-
import
|
|
425
|
+
import glendix/interop
|
|
426
|
+
import redraw.{type Element}
|
|
427
|
+
import redraw/dom/attribute.{type Attribute}
|
|
422
428
|
|
|
423
429
|
fn m() { binding.module("recharts") }
|
|
424
430
|
|
|
425
|
-
pub fn pie_chart(attrs: List(Attribute), children: List(
|
|
426
|
-
|
|
431
|
+
pub fn pie_chart(attrs: List(Attribute), children: List(Element)) -> Element {
|
|
432
|
+
interop.component_el(binding.resolve(m(), "PieChart"), attrs, children)
|
|
427
433
|
}
|
|
428
434
|
|
|
429
|
-
pub fn tooltip(attrs: List(Attribute)) ->
|
|
430
|
-
|
|
435
|
+
pub fn tooltip(attrs: List(Attribute)) -> Element {
|
|
436
|
+
interop.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
|
|
431
437
|
}
|
|
432
438
|
\`\`\`
|
|
433
439
|
|
|
@@ -484,18 +490,19 @@ gleam run -m glendix/install
|
|
|
484
490
|
|
|
485
491
|
\`\`\`gleam
|
|
486
492
|
// src/widgets/switch.gleam (자동 생성)
|
|
487
|
-
import glendix/mendix
|
|
488
|
-
import glendix/
|
|
489
|
-
import
|
|
493
|
+
import glendix/mendix.{type JsProps}
|
|
494
|
+
import glendix/interop
|
|
495
|
+
import redraw.{type Element}
|
|
496
|
+
import redraw/dom/attribute
|
|
490
497
|
import glendix/widget
|
|
491
498
|
|
|
492
499
|
/// Switch 위젯 렌더링 - props에서 속성을 읽어 위젯에 전달
|
|
493
|
-
pub fn render(props: JsProps) ->
|
|
500
|
+
pub fn render(props: JsProps) -> Element {
|
|
494
501
|
let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
|
|
495
502
|
let action = mendix.get_prop_required(props, "action")
|
|
496
503
|
|
|
497
504
|
let comp = widget.component("Switch")
|
|
498
|
-
|
|
505
|
+
interop.component_el(
|
|
499
506
|
comp,
|
|
500
507
|
[
|
|
501
508
|
attribute.attribute("booleanAttribute", boolean_attribute),
|
|
@@ -522,13 +529,14 @@ switch.render(props)
|
|
|
522
529
|
## 기술 스택
|
|
523
530
|
|
|
524
531
|
- **Gleam** → JavaScript 컴파일
|
|
525
|
-
- **[glendix](https://hexdocs.pm/glendix/)** —
|
|
532
|
+
- **[glendix](https://hexdocs.pm/glendix/)** — Mendix API + JS Interop Gleam 바인딩
|
|
533
|
+
- **[redraw](https://hexdocs.pm/redraw/)** / **[redraw_dom](https://hexdocs.pm/redraw_dom/)** — React Gleam 바인딩
|
|
526
534
|
- **Mendix Pluggable Widget** (React 19)
|
|
527
535
|
- **${pm}** — 패키지 매니저
|
|
528
536
|
|
|
529
537
|
## 라이센스
|
|
530
538
|
|
|
531
|
-
|
|
539
|
+
${license}
|
|
532
540
|
`;
|
|
533
541
|
}
|
|
534
542
|
|
|
@@ -536,26 +544,26 @@ Apache-2.0
|
|
|
536
544
|
// Japanese
|
|
537
545
|
// ---------------------------------------------------------------------------
|
|
538
546
|
|
|
539
|
-
function generateJa(names, pm, installCmd) {
|
|
547
|
+
function generateJa(names, pm, installCmd, license) {
|
|
540
548
|
return `# ${names.pascalCase}
|
|
541
549
|
|
|
542
550
|
Gleam言語で作成されたMendix Pluggable Widget。
|
|
543
551
|
|
|
544
552
|
## 基本原理
|
|
545
553
|
|
|
546
|
-
Gleam関数 \`fn(JsProps) ->
|
|
554
|
+
Gleam関数 \`fn(JsProps) -> Element\` はReact関数コンポーネントと同一のシグネチャを持つ。Reactバインディングは\`redraw\`/\`redraw_dom\`パッケージが、Mendix APIアクセスとJS interopはglendixが提供するため、ウィジェットプロジェクトではビジネスロジックにのみ集中すればよい。
|
|
547
555
|
|
|
548
556
|
\`\`\`gleam
|
|
549
557
|
// src/${names.snakeCase}.gleam
|
|
550
|
-
import glendix/mendix
|
|
551
|
-
import
|
|
552
|
-
import
|
|
553
|
-
import
|
|
558
|
+
import glendix/mendix.{type JsProps}
|
|
559
|
+
import redraw.{type Element}
|
|
560
|
+
import redraw/dom/attribute
|
|
561
|
+
import redraw/dom/html
|
|
554
562
|
|
|
555
|
-
pub fn widget(props: JsProps) ->
|
|
563
|
+
pub fn widget(props: JsProps) -> Element {
|
|
556
564
|
let sample_text = mendix.get_string_prop(props, "sampleText")
|
|
557
565
|
html.div([attribute.class("widget-hello-world")], [
|
|
558
|
-
|
|
566
|
+
html.text("Hello " <> sample_text),
|
|
559
567
|
])
|
|
560
568
|
}
|
|
561
569
|
\`\`\`
|
|
@@ -563,11 +571,12 @@ pub fn widget(props: JsProps) -> ReactElement {
|
|
|
563
571
|
Mendixの複合型もGleamから型安全に使用できる:
|
|
564
572
|
|
|
565
573
|
\`\`\`gleam
|
|
566
|
-
import glendix/mendix
|
|
574
|
+
import glendix/mendix.{type JsProps}
|
|
567
575
|
import glendix/mendix/editable_value
|
|
568
576
|
import glendix/mendix/action
|
|
577
|
+
import redraw.{type Element}
|
|
569
578
|
|
|
570
|
-
pub fn widget(props: JsProps) ->
|
|
579
|
+
pub fn widget(props: JsProps) -> Element {
|
|
571
580
|
// EditableValueへのアクセス
|
|
572
581
|
let name_attr: EditableValue = mendix.get_prop_required(props, "name")
|
|
573
582
|
let display = editable_value.display_value(name_attr)
|
|
@@ -636,7 +645,7 @@ bindings.json # 外部Reactコンポーネントバイン
|
|
|
636
645
|
package.json # npm依存関係(React、外部ライブラリなど)
|
|
637
646
|
\`\`\`
|
|
638
647
|
|
|
639
|
-
React/Mendix
|
|
648
|
+
Reactバインディングは[redraw](https://hexdocs.pm/redraw/)/[redraw_dom](https://hexdocs.pm/redraw_dom/)が、Mendix APIおよびJS Interopバインディングは[glendix](https://hexdocs.pm/glendix/)が提供する。
|
|
640
649
|
|
|
641
650
|
## 外部Reactコンポーネントの使用
|
|
642
651
|
|
|
@@ -672,17 +681,18 @@ gleam run -m glendix/install
|
|
|
672
681
|
|
|
673
682
|
\`\`\`gleam
|
|
674
683
|
import glendix/binding
|
|
675
|
-
import glendix/
|
|
676
|
-
import
|
|
684
|
+
import glendix/interop
|
|
685
|
+
import redraw.{type Element}
|
|
686
|
+
import redraw/dom/attribute.{type Attribute}
|
|
677
687
|
|
|
678
688
|
fn m() { binding.module("recharts") }
|
|
679
689
|
|
|
680
|
-
pub fn pie_chart(attrs: List(Attribute), children: List(
|
|
681
|
-
|
|
690
|
+
pub fn pie_chart(attrs: List(Attribute), children: List(Element)) -> Element {
|
|
691
|
+
interop.component_el(binding.resolve(m(), "PieChart"), attrs, children)
|
|
682
692
|
}
|
|
683
693
|
|
|
684
|
-
pub fn tooltip(attrs: List(Attribute)) ->
|
|
685
|
-
|
|
694
|
+
pub fn tooltip(attrs: List(Attribute)) -> Element {
|
|
695
|
+
interop.void_component_el(binding.resolve(m(), "Tooltip"), attrs)
|
|
686
696
|
}
|
|
687
697
|
\`\`\`
|
|
688
698
|
|
|
@@ -739,18 +749,19 @@ gleam run -m glendix/install
|
|
|
739
749
|
|
|
740
750
|
\`\`\`gleam
|
|
741
751
|
// src/widgets/switch.gleam(自動生成)
|
|
742
|
-
import glendix/mendix
|
|
743
|
-
import glendix/
|
|
744
|
-
import
|
|
752
|
+
import glendix/mendix.{type JsProps}
|
|
753
|
+
import glendix/interop
|
|
754
|
+
import redraw.{type Element}
|
|
755
|
+
import redraw/dom/attribute
|
|
745
756
|
import glendix/widget
|
|
746
757
|
|
|
747
758
|
/// Switchウィジェットのレンダリング - propsからプロパティを読み取りウィジェットに渡す
|
|
748
|
-
pub fn render(props: JsProps) ->
|
|
759
|
+
pub fn render(props: JsProps) -> Element {
|
|
749
760
|
let boolean_attribute = mendix.get_prop_required(props, "booleanAttribute")
|
|
750
761
|
let action = mendix.get_prop_required(props, "action")
|
|
751
762
|
|
|
752
763
|
let comp = widget.component("Switch")
|
|
753
|
-
|
|
764
|
+
interop.component_el(
|
|
754
765
|
comp,
|
|
755
766
|
[
|
|
756
767
|
attribute.attribute("booleanAttribute", boolean_attribute),
|
|
@@ -777,12 +788,13 @@ switch.render(props)
|
|
|
777
788
|
## 技術スタック
|
|
778
789
|
|
|
779
790
|
- **Gleam** → JavaScriptコンパイル
|
|
780
|
-
- **[glendix](https://hexdocs.pm/glendix/)** —
|
|
791
|
+
- **[glendix](https://hexdocs.pm/glendix/)** — Mendix API + JS Interop Gleamバインディング
|
|
792
|
+
- **[redraw](https://hexdocs.pm/redraw/)** / **[redraw_dom](https://hexdocs.pm/redraw_dom/)** — React Gleamバインディング
|
|
781
793
|
- **Mendix Pluggable Widget**(React 19)
|
|
782
794
|
- **${pm}** — パッケージマネージャー
|
|
783
795
|
|
|
784
796
|
## ライセンス
|
|
785
797
|
|
|
786
|
-
|
|
798
|
+
${license}
|
|
787
799
|
`;
|
|
788
800
|
}
|