A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

315 lines
8.0KB

  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 EditableText from "/app/js/ui/EditableText.js";
  5. import Renderer from "/app/js/ui/Renderer.js";
  6. import NESNameTable from "/app/js/models/NESNameTable.js";
  7. import NESPalette from "/app/js/models/NESPalette.js";
  8. import CTRLBanksStore from "/app/js/ctrls/CTRLBanksStore.js";
  9. const NTLI_TEMPLATE = "nametable-list-item-template";
  10. const NTLI_CANVAS = "nametable-img";
  11. const NTLI_TITLE = "title";
  12. const NTLI_SELECTED = "list-item-selected";
  13. var Nametables = {};
  14. var CurrentNT = "";
  15. const SCHEMA_ID="http://nespaint/NametableStoreSchema.json";
  16. JSONSchema.add({
  17. "$schema": "http://json-schema.org/draft-07/schema#",
  18. "$id": SCHEMA_ID,
  19. "type": "array",
  20. "items":{
  21. "type": "object",
  22. "properties":{
  23. "name":{
  24. "type": "string",
  25. "minLength": 1
  26. },
  27. "data":{
  28. "type": "string",
  29. "media": {
  30. "binaryEncoding": "base64"
  31. }
  32. },
  33. "bank":{
  34. "type": "string",
  35. "minLength": 1
  36. }
  37. },
  38. "required":["name", "data"]
  39. }
  40. });
  41. function HANDLE_NametableClick(e){
  42. var name = this.getAttribute("ntname");
  43. if (name !== CurrentNT){
  44. if (CurrentNT !== "")
  45. Nametables[CurrentNT].el.classList.remove(NTLI_SELECTED);
  46. CurrentNT = name;
  47. Nametables[CurrentNT].el.classList.add(NTLI_SELECTED);
  48. GlobalEvents.emit("change_surface", Nametables[CurrentNT].nametable);
  49. }
  50. }
  51. function SetElNTName(el, name){
  52. var et = new EditableText(el, "title");
  53. et.listen("value_change", (v) => {el.setAttribute("ntname", v);});
  54. et.value = name;
  55. return et;
  56. }
  57. var RenderNametableToEl = Utils.throttle(function(el, nametable){
  58. var cnv = el.querySelector("." + NTLI_CANVAS);
  59. var ctx = cnv.getContext("2d");
  60. Renderer.renderToFit(nametable, ctx);
  61. }, 500); // Only update twice a second.
  62. function HANDLE_NametableDataChange(nametable, e){
  63. RenderNametableToEl(this, nametable);
  64. }
  65. function ConnectElementToNametable(el, nametable){
  66. nametable.listen("data_changed", HANDLE_NametableDataChange.bind(el, nametable));
  67. }
  68. function CreateNametableDOMEntry(name, nametable){
  69. var baseel = document.querySelector("." + NTLI_TEMPLATE);
  70. if (!baseel){
  71. console.log("WARNING: Failed to find nametable list item template.");
  72. return null;
  73. }
  74. var el = baseel.cloneNode(true);
  75. el.classList.remove(NTLI_TEMPLATE);
  76. el.classList.remove("hidden");
  77. el.setAttribute("ntname", name);
  78. ConnectElementToNametable(el, nametable);
  79. el.addEventListener("click", HANDLE_NametableClick);
  80. baseel.parentNode.appendChild(el);
  81. setTimeout(()=>{
  82. RenderNametableToEl(el, nametable);
  83. }, 500); // Make the render call in about a half second. Allow DOM time to catch up?
  84. return el;
  85. }
  86. class CTRLNametablesStore{
  87. constructor(){
  88. var HANDLE_ChangeSurface = function(surf){
  89. if (!(surf instanceof NESNameTable)){
  90. if (CurrentNT !== ""){
  91. Nametables[CurrentNT].el.classList.remove(NTLI_SELECTED);
  92. CurrentNT = "";
  93. }
  94. } else {
  95. if (Nametables.length <= 0 || (CurrentNT !== "" && Nametables[CurrentNT].nametable !== surf)){
  96. console.log("WARNING: Nametable object being set outside of Nametables Store.");
  97. }
  98. }
  99. }
  100. GlobalEvents.listen("change_surface", HANDLE_ChangeSurface);
  101. GlobalEvents.listen("ntstore-add", (function(ev){
  102. GlobalEvents.emit("modal-close");
  103. var e = document.querySelector(".nt-store-add");
  104. if (e){
  105. var eform = e.querySelector("form");
  106. var einput = e.querySelector('input[name="storeitemname"]');
  107. if (eform && einput){
  108. var name = einput.value;
  109. eform.reset();
  110. this.createNametable(name);
  111. this.activateNametable(name);
  112. }
  113. }
  114. }).bind(this));
  115. GlobalEvents.listen("ntstore-remove", (function(e){
  116. if (CurrentNT !== "")
  117. this.removeNametable(CurrentNT);
  118. }).bind(this));
  119. }
  120. get length(){
  121. return Object.keys(Nametables).length;
  122. }
  123. get obj(){
  124. var data = [];
  125. Object.keys(Nametables).forEach((key) => {
  126. if (Nametables.hasOwnProperty(key)){
  127. var jdata = {
  128. name: key,
  129. data: Nametables[key].nametable.base64
  130. };
  131. if (Nametables[key].nametable.bank !== null){
  132. var bankname = CTRLBanksStore.getBankName(Nametables[key].nametable.bank);
  133. if (bankname !== null)
  134. jdata.bank = bankname;
  135. }
  136. data.push(jdata);
  137. }
  138. });
  139. return data;
  140. }
  141. set obj(d){
  142. var validator = JSONSchema.getValidator(SCHEMA_ID);
  143. if (validator !== null && validator(d)){
  144. this.clear();
  145. d.forEach((item) => {
  146. this.createNametable(item.name, item.data);
  147. if ("bank" in item){
  148. var bnk = CTRLBanksStore.getBank(item.bank);
  149. if (bnk !== null && item.name in Nametables)
  150. Nametables[item.name].nametable.bank = bnk;
  151. }
  152. });
  153. } else {
  154. var errs = JSONSchema.getLastErrors();
  155. if (errs !== null){
  156. console.log(errs);
  157. }
  158. throw new Error("Object failed to validate against NametableStoreSchema.");
  159. }
  160. }
  161. get json(){
  162. return JSON.stringify(this.obj);
  163. }
  164. set json(j){
  165. try {
  166. this.obj = JSON.parse(j);
  167. } catch (e) {
  168. throw e;
  169. }
  170. }
  171. get currentNametable(){
  172. return (CurrentNT === "") ? null : Nametables[CurrentNT].nametable;
  173. }
  174. get currentNTName(){
  175. return CurrentNT;
  176. }
  177. get keys(){
  178. return Object.keys(Nametables);
  179. }
  180. initialize(){
  181. //if (this.length <= 0){
  182. // this.createNametable("Nametable");
  183. //}
  184. return this;
  185. }
  186. createNametable(name, bbase64){
  187. if (!(name in Nametables)){
  188. var nametable = new NESNameTable();
  189. if (typeof(bbase64) === "string"){
  190. try {
  191. nametable.base64 = bbase64;
  192. } catch (e) {
  193. console.log("Failed to create Nametable. " + e.toString());
  194. nametable = null;
  195. }
  196. }
  197. if (nametable !== null){
  198. var el = CreateNametableDOMEntry(name, nametable);
  199. if (el){
  200. var elname = SetElNTName(el, name);
  201. Nametables[name] = {nametable:nametable, el:el, elname:elname};
  202. if (this.length <= 1){
  203. Nametables[name].el.click();
  204. }
  205. }
  206. }
  207. }
  208. return this;
  209. }
  210. removeNametable(name){
  211. if (name in Nametables){
  212. if (name === CurrentNT){
  213. var keys = Object.keys(Nametables);
  214. if (keys.length > 1){
  215. CurrentNT = (keys[0] !== name) ? keys[0] : keys[1];
  216. } else {
  217. CurrentNT = "";
  218. }
  219. }
  220. Nametables[name].el.parentNode.removeChild(Nametables[name].el);
  221. delete Nametables[name];
  222. if (CurrentNT !== ""){
  223. Nametables[CurrentNT].el.click();
  224. } else {
  225. GlobalEvents.emit("change_surface", null);
  226. }
  227. }
  228. return this;
  229. }
  230. renameNametable(name, newname){
  231. if ((name in Nametables) && !(newname in Nametables)){
  232. Nametables[newname] = Nametables[name];
  233. Nametables[newname].elname.value = newname;
  234. delete Nametables[name];
  235. }
  236. return this;
  237. }
  238. activateNametable(name){
  239. if (CurrentNT !== name && (name in Nametables)){
  240. Nametables[name].el.click();
  241. }
  242. return this;
  243. }
  244. getNametableName(nametable){
  245. if (!(nametable instanceof NESNameTable))
  246. throw new TypeError("Expected NESNameTable instance.");
  247. var keys = Object.keys(Nametables);
  248. for (let i=0; i < keys.length; i++){
  249. if (Nametables[keys[i]].nametable.eq(nametable))
  250. return keys[i];
  251. }
  252. return null;
  253. }
  254. getNametable(name){
  255. return (name in Nametables) ? Nametables[name].nametable : null;
  256. }
  257. clear(){
  258. Object.keys(Nametables).forEach((item) => {
  259. Nametables[item].el.parentNode.removeChild(Nametables[item].el);
  260. });
  261. Nametables = {};
  262. if (CurrentNT !== ""){
  263. CurrentNT = "";
  264. GlobalEvents.emit("change_surface", null);
  265. }
  266. }
  267. }
  268. const instance = new CTRLNametablesStore();
  269. export default instance;