|
/* |
|
* 定数を定義 |
|
*/ |
|
const STORAGE_KEY = |
|
"https://gist.github.com/sounisi5011/946c96f18c1314bec70569cd7a82d3e6"; |
|
|
|
/* |
|
* 汎用関数を定義 |
|
*/ |
|
|
|
let loadedData; |
|
|
|
/* |
|
* sessionStorageからデータを取得する |
|
*/ |
|
function loadData() { |
|
if (loadedData) return loadedData; |
|
|
|
const dataText = sessionStorage.getItem(STORAGE_KEY); |
|
|
|
if (dataText) { |
|
try { |
|
loadedData = JSON.parse(dataText); |
|
return loadedData; |
|
} catch (error) { |
|
if (!(error instanceof SyntaxError)) throw error; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/* |
|
* sessionStorageにデータを設定する |
|
*/ |
|
function setData(object) { |
|
const newData = Object.assign({}, loadData(), object); |
|
loadedData = newData; |
|
|
|
const dataText = JSON.stringify(newData); |
|
sessionStorage.setItem(STORAGE_KEY, dataText); |
|
} |
|
|
|
/* |
|
* JSON5断片を入力欄から読み取る |
|
* Note: 正式なJSONにはコメントも書けないが、JSON5はコメントも書けるしプロパティ名をクォートで囲む必要もない便利なもの。 |
|
*/ |
|
function readJson5InputValue(elem, replacer) { |
|
const jsonText = replacer(`\n${elem.value}\n`); |
|
try { |
|
const jsonData = JSON5.parse(jsonText); |
|
elem.setCustomValidity(""); |
|
return jsonData; |
|
} catch (e) { |
|
elem.setCustomValidity("不正な形式のJSONです"); |
|
return undefined; |
|
} |
|
} |
|
|
|
// ----- ----- ----- ----- ----- // |
|
|
|
/* |
|
* data-value-set属性を持つselect要素内にoption要素を追加 |
|
*/ |
|
document.querySelectorAll("select[data-value-set]").forEach((elem) => { |
|
const valueList = elem.dataset.valueSet.split(/\s*[\s,]\s*/g); |
|
const replaceText = elem.dataset.valueReplace; |
|
valueList.forEach((value) => { |
|
const optionElem = document.createElement("option"); |
|
if (replaceText) { |
|
optionElem.value = value; |
|
optionElem.textContent = replaceText.replace(/%s/g, value); |
|
} else { |
|
optionElem.textContent = value; |
|
} |
|
elem.appendChild(optionElem); |
|
}); |
|
}); |
|
|
|
/* |
|
* HTMLの要素に対応するDOM要素オブジェクトを取得 |
|
*/ |
|
const rootFormElem = document.getElementById("root-form"); |
|
const rootFormItems = rootFormElem.elements; |
|
const speciesDescriptionInputElemList = [ |
|
...document.querySelectorAll("[data-input-group=species-description]"), |
|
]; |
|
|
|
const interactGroupElem = document.getElementById("group-interact"); |
|
const orientationsGroupElem = document.getElementById("group-orientations"); |
|
const collisionGroupElem = document.getElementById( |
|
"group-orientations-collision" |
|
); |
|
|
|
const dlButtonElem = document.getElementById("download-button"); |
|
const previewAreaElem = document.getElementById("preview-area"); |
|
|
|
/* |
|
* JSONオブジェクト、JSON文字列、ファイル名を生成する。また、生成の際に、HTML上の表示も更新する |
|
*/ |
|
function generateJsonData() { |
|
const objectName = rootFormItems.objectName.value; |
|
const interactDataConfigName = |
|
rootFormItems["interactData.config"].value || objectName; |
|
const description = rootFormItems.description.value; |
|
|
|
/* |
|
* 種族別のアイテムの説明文を生成 |
|
* 全ての入力値が空文字列の場合は、descriptionの内容を挿入する。 |
|
*/ |
|
const isEmptyAllSpeciesDescription = speciesDescriptionInputElemList |
|
.map((elem) => elem.value) |
|
.every((text) => text === ""); |
|
const speciesDescriptionData = {}; |
|
|
|
speciesDescriptionInputElemList.forEach((elem) => { |
|
const name = elem.name; |
|
let defaultSpeciesDescription = ""; |
|
|
|
speciesDescriptionData[name] = isEmptyAllSpeciesDescription |
|
? (defaultSpeciesDescription = description) |
|
: elem.value; |
|
|
|
elem.placeholder = defaultSpeciesDescription; |
|
Stretchy.resize(elem); |
|
}); |
|
|
|
/* |
|
* JSONデータを生成 |
|
*/ |
|
const jsonData = Object.assign( |
|
{ |
|
objectName: objectName, |
|
colonyTags: [objectName], |
|
printable: rootFormItems.printable.value === "true", |
|
price: rootFormItems.price.valueAsNumber, |
|
rarity: rootFormItems.rarity.value, |
|
interactAction: rootFormItems.interactAction.value, |
|
interactData: { |
|
config: `/interface/windowconfig/${interactDataConfigName}.config`, |
|
filter: readJson5InputValue( |
|
rootFormItems["interactData.filter"], |
|
(s) => `[${s}]` |
|
), |
|
}, |
|
description: description, |
|
shortdescription: rootFormItems.shortdescription.value, |
|
race: rootFormItems.race.value, |
|
category: rootFormItems.category.value, |
|
}, |
|
speciesDescriptionData, |
|
{ |
|
learnBlueprintsOnPickup: readJson5InputValue( |
|
rootFormItems.learnBlueprintsOnPickup, |
|
(s) => `[${s}]` |
|
), |
|
|
|
inventoryIcon: `${objectName}Icon.png`, |
|
orientations: [ |
|
{ |
|
dualImage: `${objectName}.png:<color>.<flame>`, |
|
direction: rootFormItems["orientations.direction"].value, |
|
flipimages: rootFormItems["orientations.flipimages"].value === "true", |
|
imagePosition: [ |
|
rootFormItems["orientations.imagePosition[0]"].valueAsNumber, |
|
rootFormItems["orientations.imagePosition[1]"].valueAsNumber, |
|
], |
|
frames: rootFormItems["orientations.frames"].valueAsNumber, |
|
animationCycle: |
|
rootFormItems["orientations.animationCycle"].valueAsNumber, |
|
spaces: readJson5InputValue( |
|
rootFormItems["orientations.spaces"], |
|
(s) => `[${s}]` |
|
), |
|
collision: rootFormItems["orientations.collision"].value || undefined, |
|
anchors: [rootFormItems["orientations.anchors"].value], |
|
}, |
|
], |
|
} |
|
); |
|
|
|
/* |
|
* colonyTagsの値を表示 |
|
*/ |
|
rootFormItems.colonyTags.textContent = JSON.stringify(jsonData.colonyTags); |
|
|
|
/* |
|
* interactActionが空文字列の場合はinteractActionとinteractDataを削除 |
|
*/ |
|
if (jsonData.interactAction === "") { |
|
delete jsonData.interactAction; |
|
delete jsonData.interactData; |
|
interactGroupElem.classList.add("hidden"); |
|
} else { |
|
interactGroupElem.classList.remove("hidden"); |
|
} |
|
|
|
/* |
|
* interactData内のconfigの初期値を設定 |
|
*/ |
|
rootFormItems["interactData.config"].placeholder = interactDataConfigName; |
|
Stretchy.resize(rootFormItems["interactData.config"]); |
|
|
|
/* |
|
* inventoryIconの値を表示 |
|
*/ |
|
rootFormItems.inventoryIcon.textContent = JSON.stringify( |
|
jsonData.inventoryIcon |
|
); |
|
rootFormItems["orientations.dualImage"].textContent = JSON.stringify( |
|
jsonData.orientations[0].dualImage |
|
); |
|
|
|
/* |
|
* orientations内のcollisionが未入力の場合はcollisionの入力欄を半透明表示に変更 |
|
*/ |
|
collisionGroupElem.classList.toggle( |
|
"hidden", |
|
!jsonData.orientations[0].collision |
|
); |
|
|
|
/* |
|
* orientationsのチェックボックスが外れている場合はorientationsを削除 |
|
*/ |
|
if (rootFormItems["orientations-visibility"].checked) { |
|
orientationsGroupElem.classList.remove("hidden"); |
|
} else { |
|
delete jsonData.orientations; |
|
orientationsGroupElem.classList.add("hidden"); |
|
} |
|
|
|
/* |
|
* sessionStorageに入力欄のデータを一時保存 |
|
*/ |
|
setData( |
|
Array.from(rootFormItems) |
|
.filter( |
|
(elem) => elem.name && /^(?:input|textarea|select)$/i.test(elem.tagName) |
|
) |
|
.reduce((saveData, elem) => { |
|
saveData[elem.name] = /^checkbox$/i.test(elem.type) |
|
? elem.checked |
|
: elem.value; |
|
return saveData; |
|
}, {}) |
|
); |
|
|
|
/* |
|
* 入力欄の入力内容が正しいか検証 |
|
*/ |
|
if (!rootFormElem.checkValidity()) { |
|
delete previewAreaElem.dataset.filename; |
|
previewAreaElem.textContent = |
|
"入力された値が間違っています。赤の入力欄を修正してください"; |
|
return null; |
|
} |
|
|
|
/* |
|
* ファイル名を生成 |
|
*/ |
|
const filenameStr = `${jsonData.objectName}.object`; |
|
|
|
/* |
|
* データをJSON文字列に変換 |
|
*/ |
|
const jsonText = prettyJSONStringify(jsonData, { |
|
// インデントはスペース2文字 |
|
tab: " ", |
|
// 配列の"["と"]"の内側にスペース1文字を入れる |
|
spaceInsideArray: " ", |
|
shouldExpand(obj, level, index) { |
|
// 直下のinteractDataフィールドの内容は常に折り返す |
|
if (level === 1 && index === "interactData") return true; |
|
// 80文字を超える長さの値は折り返す |
|
return JSON.stringify(obj).length > 80; |
|
}, |
|
}) |
|
// 数字だけが入った配列の"["と"]"の内側にあるスペース文字を削除 |
|
.replace( |
|
/\[\s*((?:\s*(?:,\s*)?-?\d+(?:\.\d+)?(?:[Ee][+-]?\d+)?)+)\s*\]/g, |
|
(_, v) => `[${v}]` |
|
); |
|
|
|
/* |
|
* JSONのプレビューを更新する |
|
*/ |
|
// ファイル名を設定 |
|
previewAreaElem.dataset.filename = filenameStr; |
|
// JSON文字列をHTMLに反映 |
|
previewAreaElem.textContent = jsonText; |
|
// highlight.jsでシンタックスハイライトを適用 |
|
hljs.highlightBlock(previewAreaElem); |
|
|
|
return { |
|
obj: jsonData, |
|
filename: filenameStr, |
|
text: jsonText, |
|
}; |
|
} |
|
|
|
/* |
|
* 初期化する |
|
*/ |
|
function init() { |
|
/* |
|
* sessionStorageの一時保存データを入力欄に反映する |
|
*/ |
|
const savedData = loadData(); |
|
if (savedData) { |
|
Object.keys(savedData).forEach((name) => { |
|
const formItemElem = rootFormItems[name]; |
|
if (formItemElem) { |
|
if (/^checkbox$/i.test(formItemElem.type)) { |
|
formItemElem.checked = savedData[name]; |
|
} else { |
|
formItemElem.value = savedData[name]; |
|
Stretchy.resize(formItemElem); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
generateJsonData(); |
|
} |
|
|
|
/* |
|
* 入力処理を設定 |
|
*/ |
|
rootFormElem.addEventListener("input", generateJsonData); |
|
rootFormElem.addEventListener("change", generateJsonData); |
|
|
|
/* |
|
* フォームの送信を禁止 |
|
*/ |
|
rootFormElem.addEventListener("submit", (event) => { |
|
event.preventDefault(); |
|
}); |
|
|
|
/* |
|
* ダウンロードボタンの処理を設定 |
|
*/ |
|
dlButtonElem.addEventListener("click", () => { |
|
/* |
|
* フォームの入力内容を検証 |
|
*/ |
|
if (!rootFormElem.reportValidity()) return; |
|
|
|
const jsonData = generateJsonData(); |
|
if (!jsonData) { |
|
alert("入力された値が間違っています。赤の入力欄を修正してください"); |
|
return; |
|
} |
|
|
|
/* |
|
* JSON文字列を元に、ファイルダウンロード用のオブジェクトURLを生成 |
|
*/ |
|
const blob = new Blob([jsonData.text], { type: "application/json" }); |
|
const downloadURL = URL.createObjectURL(blob); |
|
|
|
/* |
|
* オブジェクトURLを設定したa要素をクリックし、ダウンロードダイアログを表示 |
|
*/ |
|
const aElem = document.createElement("a"); |
|
aElem.href = downloadURL; |
|
aElem.download = jsonData.filename; |
|
aElem.click(); |
|
}); |
|
|
|
init(); |