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个字符

315 行
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;