A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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;