A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

295 lines
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;