A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

295 行
8.1KB

  1. import GlobalEvents from "/app/js/common/EventCaller.js";
  2. import Utils from "/app/js/common/Utils.js";
  3. import JSONSchema from "/app/js/common/JSONSchema.js";
  4. import NESBank from "/app/js/models/NESBank.js";
  5. import NESPalette from "/app/js/models/NESPalette.js";
  6. import NESNameTable from "/app/js/models/NESNameTable.js";
  7. import CTRLPalettesStore from "/app/js/ctrls/CTRLPalettesStore.js";
  8. import CTRLBanksStore from "/app/js/ctrls/CTRLBanksStore.js";
  9. import CTRLNameTablesStore from "/app/js/ctrls/CTRLNameTablesStore.js";
  10. const SUPPORTED_PROJECT_VERSIONS=[
  11. "0.1"
  12. ];
  13. const PROJECT_ID="NESPProj"
  14. const SCHEMA_ID = "http://nespaint/Project.json";
  15. JSONSchema.add({
  16. "$schema": "http://json-schema.org/draft-07/schema#",
  17. "$id": SCHEMA_ID,
  18. "type":"object",
  19. "properties":{
  20. "id":{
  21. "type":"string",
  22. "enum":["NESPProj"]
  23. },
  24. "version":{
  25. "type":"string",
  26. "pattern":"^[0-9]{1,}\.[0-9]{1,}$"
  27. },
  28. "paletteStore":{"$ref":"http://nespaint/PalettesStoreSchema.json"},
  29. "bankStore":{"$ref":"http://nespaint/BanksStoreSchema.json"},
  30. "nametableStore":{"$ref":"http://nespaint/NametableStoreSchema.json"}
  31. },
  32. "required":["id","version","paletteStore","bankStore"]
  33. });
  34. var SURF = null;
  35. function JSONFromProject(){
  36. var proj = {
  37. id:PROJECT_ID,
  38. version:SUPPORTED_PROJECT_VERSIONS[SUPPORTED_PROJECT_VERSIONS.length - 1],
  39. paletteStore:CTRLPalettesStore.obj,
  40. bankStore:CTRLBanksStore.obj
  41. };
  42. if (CTRLNameTablesStore.keys.length > 0)
  43. proj.nametableStore = CTRLNameTablesStore.obj;
  44. return JSON.stringify(proj);
  45. }
  46. function RequestDownload(filename, datblob){
  47. var a = document.createElement("a");
  48. a.href = window.URL.createObjectURL(datblob);
  49. a.download = filename;
  50. var body = document.querySelector("body");
  51. body.appendChild(a);
  52. a.click();
  53. setTimeout(function(){ // fixes firefox html removal bug
  54. window.URL.revokeObjectURL(a.href);
  55. a.remove();
  56. }, 500);
  57. }
  58. function LoadFile(file){
  59. if (SURF !== null){
  60. var reader = new FileReader();
  61. if (SURF instanceof NESBank){
  62. reader.onload = function(e){
  63. try {
  64. SURF.chr = new Uint8Array(e.target.result);
  65. } catch (e) {
  66. console.log(e.toString());
  67. }
  68. }
  69. reader.readAsArrayBuffer(file);
  70. }
  71. }
  72. }
  73. function HANDLE_DragOver(e){
  74. e.stopPropagation();
  75. e.preventDefault();
  76. e.dataTransfer.dropEffect = 'copy';
  77. };
  78. function HANDLE_FileDrop(e){
  79. e.stopPropagation();
  80. e.preventDefault();
  81. var files = e.dataTransfer.files;
  82. for (let i=0; i < files.length; i++){
  83. LoadFile(files[i]);
  84. }
  85. }
  86. function HANDLE_NewProject(){
  87. GlobalEvents.emit("modal-close");
  88. CTRLNameTablesStore.clear();
  89. CTRLBanksStore.clear();
  90. CTRLPalettesStore.clear();
  91. CTRLPalettesStore.createPalette("Palette 1");
  92. CTRLBanksStore.createBank("Bank 1");
  93. }
  94. function HANDLE_SaveProject(e){
  95. //var a = document.createElement("a");
  96. var file = new Blob([JSONFromProject()], {type: "text/plain"});
  97. RequestDownload("nesproject.json", file);
  98. }
  99. function HANDLE_LoadProjectRequest(){
  100. var input = document.querySelectorAll("input.project-loader");
  101. if (input.length > 0){
  102. input[0].click();
  103. }
  104. }
  105. function HANDLE_LoadProject(e){
  106. if (this.files && this.files.length > 0){
  107. var reader = new FileReader();
  108. reader.onload = (function(e) {
  109. var o = null;
  110. var validator = JSONSchema.getValidator(SCHEMA_ID);
  111. try {
  112. o = JSON.parse(e.target.result);
  113. } catch (e) {
  114. console.log("Failed to parse JSON string. " + e.toString());
  115. }
  116. if (validator !== null && validator(o)){
  117. if (o.id !== PROJECT_ID){
  118. console.log("ERROR: Project ID is invalid. Canceling project load.");
  119. return;
  120. }
  121. if (SUPPORTED_PROJECT_VERSIONS.indexOf(o.version) < 0){
  122. console.log("ERROR: Project file version does not match any supported version numbers. Canceling project load.");
  123. return;
  124. }
  125. // NOTE: For future... branch out to specific version loaders as needed.
  126. // Since only one version is currently supported, however, just continue on, my friends!
  127. CTRLPalettesStore.obj = o.paletteStore;
  128. CTRLBanksStore.obj = o.bankStore;
  129. if ("nametableStore" in o)
  130. CTRLNameTablesStore.obj = o.nametableStore;
  131. }
  132. if (this.parentNode.nodeName.toLowerCase() === "form"){
  133. this.parentNode.reset();
  134. } else {
  135. console.log("WARNING: Parent node is NOT a <form> element.");
  136. }
  137. }).bind(this);
  138. reader.readAsText(this.files[0]);
  139. } else {
  140. console.log("Project file not found or no file selected.");
  141. }
  142. }
  143. function HANDLE_ExportCHR(e){
  144. e.preventDefault();
  145. var bank = CTRLBanksStore.currentBank;
  146. if (bank !== null){
  147. var dat = null;
  148. var size = document.querySelector('input[name="exportchr-size"]:checked').value;
  149. switch (size){
  150. case "full":
  151. dat = bank.getCHR(0,0);
  152. break;
  153. case "current":
  154. dat = bank.chr;
  155. }
  156. if (dat !== null){
  157. var file = new Blob([dat], {type:"application/octet-stream"});
  158. var filename = CTRLBanksStore.currentBankName.replace(/[^a-z0-9\-_.]/gi, '_').toLowerCase() + ".chr";
  159. RequestDownload(filename, file);
  160. }
  161. }
  162. GlobalEvents.emit("modal-close");
  163. }
  164. function HANDLE_ExportPalASM(e){
  165. var pal = CTRLPalettesStore.currentPalette;
  166. var palname = CTRLPalettesStore.currentPaletteName.replace(/[^a-z0-9\-_.]/gi, '_');
  167. if (pal !== null && palname !== ""){
  168. var asmtxt = pal.to_asm(palname);
  169. var file = new Blob([asmtxt], {type: "text/plain"});
  170. RequestDownload(palname + ".asm", file);
  171. }
  172. }
  173. function HANDLE_ExportNameTableASM(e){
  174. var nt = CTRLNameTablesStore.currentNametable;
  175. if (nt !== null){
  176. var basename = CTRLNameTablesStore.currentNTName.replace(/[^a-z0-9\-_.]/gi, '_');
  177. var mode = document.querySelector('input[name="exportnt-op"]:checked').value;
  178. var asm = "";
  179. switch (mode){
  180. case "both":
  181. asm = nt.to_asm(basename + "_NT", basename + "_AT");
  182. break;
  183. case "nametable":
  184. asm = nt.nametable_asm(basename + "_NT");
  185. break;
  186. case "attribtable":
  187. asm = nt.attribtable_asm(basename + "_AT");
  188. break;
  189. }
  190. if (asm !== ""){
  191. var file = new Blob([asm], {type: "text/plain"});
  192. RequestDownload(basename + ".asm", file);
  193. }
  194. }
  195. }
  196. function HANDLE_SurfChange(surf){
  197. var enableclass = "";
  198. if (surf instanceof NESBank){
  199. SURF = surf;
  200. enableclass = "surf-bank";
  201. } else {
  202. SURF = null;
  203. if (surf instanceof NESNameTable)
  204. enableclass="surf-nametable";
  205. }
  206. var e = document.querySelectorAll(".surf-export");
  207. for (let i=0; i < e.length; i++){
  208. var ea = e[i].querySelector("a");
  209. if (ea){
  210. if (e[i].classList.contains(enableclass)){
  211. e[i].classList.remove("disable-links");
  212. ea.classList.remove("pure-menu-disabled");
  213. } else {
  214. e[i].classList.add("disable-links");
  215. ea.classList.add("pure-menu-disabled");
  216. }
  217. }
  218. }
  219. }
  220. class CTRLIO{
  221. constructor(){
  222. GlobalEvents.listen("change_surface", HANDLE_SurfChange);
  223. GlobalEvents.listen("save-project", HANDLE_SaveProject);
  224. GlobalEvents.listen("load-project", HANDLE_LoadProjectRequest);
  225. GlobalEvents.listen("export-pal-asm", HANDLE_ExportPalASM);
  226. GlobalEvents.listen("export-nametable", HANDLE_ExportNameTableASM);
  227. GlobalEvents.listen("new-project", HANDLE_NewProject);
  228. var input = document.querySelectorAll("input.project-loader");
  229. if (input.length > 0){
  230. input[0].addEventListener("change", HANDLE_LoadProject);
  231. }
  232. }
  233. initialize(){
  234. // Connecting drag/drop ability.
  235. var e = document.querySelectorAll(".drop-zone");
  236. for (let i=0; i < e.length; e++){
  237. e[i].addEventListener("dragover", HANDLE_DragOver);
  238. e[i].addEventListener("drop", HANDLE_FileDrop);
  239. }
  240. // Connecting to button for export request.
  241. var e = document.querySelectorAll(".export-chr-btn");
  242. for (let i=0; i < e.length; i++){
  243. e[i].addEventListener("click", HANDLE_ExportCHR);
  244. }
  245. CTRLPalettesStore.initialize();
  246. CTRLBanksStore.initialize();
  247. CTRLNameTablesStore.initialize();
  248. }
  249. }
  250. const instance = new CTRLIO();
  251. export default instance;