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">
|
||||
<label class="form-label mb-0">Input Fields (JSON)</label>
|
||||
<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="copyFieldJson('inputFields')">복사</button>
|
||||
</div>
|
||||
@@ -516,6 +517,7 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<label class="form-label mb-0">Output Fields (JSON)</label>
|
||||
<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="copyFieldJson('outputFields')">복사</button>
|
||||
</div>
|
||||
@@ -615,6 +617,41 @@
|
||||
<div id="resultBox"></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 -->
|
||||
<div class="modal fade" id="editToolModal" tabindex="-1" aria-labelledby="editToolModalLabel" aria-hidden="true">
|
||||
<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() {
|
||||
const tbody = document.getElementById('toolListBody');
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5 text-muted">Loading data...</td></tr>';
|
||||
|
||||
Reference in New Issue
Block a user