feat: add scaffold field editor
Some checks failed
Deploy to OCIWP / deploy (push) Has been cancelled
Some checks failed
Deploy to OCIWP / deploy (push) Has been cancelled
This commit is contained in:
@@ -506,6 +506,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
<label class="form-label mb-0">Input Fields (JSON)</label>
|
<label class="form-label mb-0">Input Fields (JSON)</label>
|
||||||
<div class="d-flex gap-1">
|
<div class="d-flex gap-1">
|
||||||
|
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="openFieldEditor('inputFields')">필드 편집</button>
|
||||||
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('inputFields')">예제 넣기</button>
|
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('inputFields')">예제 넣기</button>
|
||||||
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="copyFieldJson('inputFields')">복사</button>
|
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="copyFieldJson('inputFields')">복사</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -516,6 +517,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
<label class="form-label mb-0">Output Fields (JSON)</label>
|
<label class="form-label mb-0">Output Fields (JSON)</label>
|
||||||
<div class="d-flex gap-1">
|
<div class="d-flex gap-1">
|
||||||
|
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="openFieldEditor('outputFields')">필드 편집</button>
|
||||||
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('outputFields')">예제 넣기</button>
|
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="loadFieldExample('outputFields')">예제 넣기</button>
|
||||||
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="copyFieldJson('outputFields')">복사</button>
|
<button type="button" class="btn-secondary-action" style="padding: 0.2rem 0.45rem; font-size: 0.72rem;" onclick="copyFieldJson('outputFields')">복사</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -615,6 +617,41 @@
|
|||||||
<div id="resultBox"></div>
|
<div id="resultBox"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Field Editor Modal -->
|
||||||
|
<div class="modal fade" id="fieldEditorModal" tabindex="-1" aria-labelledby="fieldEditorModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="fieldEditorModalLabel">필드 편집</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="input-hint mt-0 mb-3">행을 추가해 필드를 입력하면 저장할 때 JSON으로 자동 변환됩니다.</p>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle" id="fieldEditorTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="min-width: 150px;">Name</th>
|
||||||
|
<th style="min-width: 125px;">Type</th>
|
||||||
|
<th style="min-width: 210px;">Description</th>
|
||||||
|
<th style="min-width: 180px;">Example</th>
|
||||||
|
<th style="min-width: 125px;">Required</th>
|
||||||
|
<th style="width: 1%;">삭제</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="fieldEditorBody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-secondary-action" onclick="addFieldEditorRow()">+ 필드 추가</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn-secondary-action" data-bs-dismiss="modal" style="padding: 0.5rem 1rem; font-size: 0.875rem;">취소</button>
|
||||||
|
<button type="button" class="btn-action" onclick="applyFieldEditor()">JSON에 적용</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Edit Tool Modal -->
|
<!-- Edit Tool Modal -->
|
||||||
<div class="modal fade" id="editToolModal" tabindex="-1" aria-labelledby="editToolModalLabel" aria-hidden="true">
|
<div class="modal fade" id="editToolModal" tabindex="-1" aria-labelledby="editToolModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@@ -746,6 +783,112 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fieldTypes = ['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal'];
|
||||||
|
let fieldEditorTargetId = null;
|
||||||
|
let fieldEditorModalInstance = null;
|
||||||
|
|
||||||
|
function openFieldEditor(targetId) {
|
||||||
|
const textarea = document.getElementById(targetId);
|
||||||
|
let fields = [];
|
||||||
|
if (textarea.value.trim()) {
|
||||||
|
try {
|
||||||
|
fields = JSON.parse(textarea.value);
|
||||||
|
if (!Array.isArray(fields)) throw new Error('not an array');
|
||||||
|
} catch (error) {
|
||||||
|
alert('현재 JSON 형식이 올바르지 않습니다. JSON을 수정한 후 다시 열어주세요.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldEditorTargetId = targetId;
|
||||||
|
document.getElementById('fieldEditorModalLabel').textContent = targetId === 'inputFields'
|
||||||
|
? 'Input Fields 편집'
|
||||||
|
: 'Output Fields 편집';
|
||||||
|
const body = document.getElementById('fieldEditorBody');
|
||||||
|
body.replaceChildren();
|
||||||
|
(fields.length ? fields : [{}]).forEach(addFieldEditorRow);
|
||||||
|
|
||||||
|
if (!fieldEditorModalInstance) {
|
||||||
|
fieldEditorModalInstance = new bootstrap.Modal(document.getElementById('fieldEditorModal'));
|
||||||
|
}
|
||||||
|
fieldEditorModalInstance.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFieldEditorRow(field = {}) {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
const makeInput = (value, placeholder) => {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'text';
|
||||||
|
input.className = 'form-control form-control-sm';
|
||||||
|
input.value = value || '';
|
||||||
|
input.placeholder = placeholder;
|
||||||
|
return input;
|
||||||
|
};
|
||||||
|
const nameInput = makeInput(field.name, 'e.g. employeeId');
|
||||||
|
nameInput.dataset.field = 'name';
|
||||||
|
const descriptionInput = makeInput(field.description, 'e.g. Employee ID');
|
||||||
|
descriptionInput.dataset.field = 'description';
|
||||||
|
const exampleInput = makeInput(field.example, 'e.g. EMP10001');
|
||||||
|
exampleInput.dataset.field = 'example';
|
||||||
|
|
||||||
|
const typeSelect = document.createElement('select');
|
||||||
|
typeSelect.className = 'form-select form-select-sm';
|
||||||
|
typeSelect.dataset.field = 'type';
|
||||||
|
fieldTypes.forEach(type => {
|
||||||
|
const option = new Option(type, type, false, (field.type || 'String') === type);
|
||||||
|
typeSelect.add(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
const requiredSelect = document.createElement('select');
|
||||||
|
requiredSelect.className = 'form-select form-select-sm';
|
||||||
|
requiredSelect.dataset.field = 'required';
|
||||||
|
requiredSelect.add(new Option('Required', 'true', false, field.required === true || field.required === 'true'));
|
||||||
|
requiredSelect.add(new Option('Optional', 'false', false, !(field.required === true || field.required === 'true')));
|
||||||
|
|
||||||
|
[nameInput, typeSelect, descriptionInput, exampleInput, requiredSelect].forEach(control => {
|
||||||
|
const cell = document.createElement('td');
|
||||||
|
cell.appendChild(control);
|
||||||
|
row.appendChild(cell);
|
||||||
|
});
|
||||||
|
const deleteCell = document.createElement('td');
|
||||||
|
const deleteButton = document.createElement('button');
|
||||||
|
deleteButton.type = 'button';
|
||||||
|
deleteButton.className = 'btn-secondary-action';
|
||||||
|
deleteButton.textContent = '삭제';
|
||||||
|
deleteButton.addEventListener('click', () => row.remove());
|
||||||
|
deleteCell.appendChild(deleteButton);
|
||||||
|
row.appendChild(deleteCell);
|
||||||
|
document.getElementById('fieldEditorBody').appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFieldEditor() {
|
||||||
|
const rows = [...document.querySelectorAll('#fieldEditorBody tr')];
|
||||||
|
const names = new Set();
|
||||||
|
const fields = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
const name = row.querySelector('[data-field="name"]').value.trim();
|
||||||
|
if (!name) continue;
|
||||||
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
|
||||||
|
alert(`필드명 "${name}"은(는) Java 필드명 형식으로 입력해주세요.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (names.has(name)) {
|
||||||
|
alert(`필드명 "${name}"이(가) 중복되었습니다.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
names.add(name);
|
||||||
|
fields.push({
|
||||||
|
name,
|
||||||
|
type: row.querySelector('[data-field="type"]').value,
|
||||||
|
description: row.querySelector('[data-field="description"]').value.trim(),
|
||||||
|
example: row.querySelector('[data-field="example"]').value.trim(),
|
||||||
|
required: row.querySelector('[data-field="required"]').value === 'true'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.getElementById(fieldEditorTargetId).value = JSON.stringify(fields, null, 2);
|
||||||
|
fieldEditorModalInstance.hide();
|
||||||
|
}
|
||||||
|
|
||||||
function loadToolList() {
|
function loadToolList() {
|
||||||
const tbody = document.getElementById('toolListBody');
|
const tbody = document.getElementById('toolListBody');
|
||||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5 text-muted">Loading data...</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5 text-muted">Loading data...</td></tr>';
|
||||||
|
|||||||
Reference in New Issue
Block a user