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

280 行
6.5KB

  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 NESBank from "/app/js/models/NESBank.js";
  7. import NESPalette from "/app/js/models/NESPalette.js";
  8. const BLI_TEMPLATE = "bank-list-item-template";
  9. const BLI_CANVAS = "bank-img";
  10. const BLI_TITLE = "title";
  11. const BLI_SELECTED = "list-item-selected";
  12. var Banks = {};
  13. var CurrentBank = "";
  14. JSONSchema.add({
  15. "$schema": "http://json-schema.org/draft-07/schema#",
  16. "$id": "BanksStoreSchema.json",
  17. "type": "array",
  18. "items":{
  19. "type": "object",
  20. "properties":{
  21. "name":{
  22. "type": "string",
  23. "minLength": 1
  24. },
  25. "data":{
  26. "type": "string",
  27. "media": {
  28. "binaryEncoding": "base64"
  29. }
  30. }
  31. },
  32. "required":["name", "data"]
  33. }
  34. });
  35. function HANDLE_BankClick(e){
  36. var name = this.getAttribute("bankname");
  37. if (name !== CurrentBank){
  38. if (CurrentBank !== "")
  39. Banks[CurrentBank].el.classList.remove(BLI_SELECTED);
  40. CurrentBank = name;
  41. Banks[CurrentBank].el.classList.add(BLI_SELECTED);
  42. GlobalEvents.emit("change_surface", Banks[CurrentBank].bank);
  43. }
  44. }
  45. function SetElBankName(el, name){
  46. var et = new EditableText(el, "title");
  47. et.listen("value_change", (v) => {el.setAttribute("bankname", v);});
  48. et.value = name;
  49. return et;
  50. //var sel = el.querySelector("." + BLI_TITLE);
  51. //if (sel){
  52. // sel.innerHTML = name;
  53. //}
  54. }
  55. var RenderBankToEl = Utils.throttle(function(el, bank){
  56. var cnv = el.querySelector("." + BLI_CANVAS);
  57. var ctx = cnv.getContext("2d");
  58. Renderer.renderToFit(bank, ctx);
  59. }, 500); // Only update twice a second.
  60. function HANDLE_BankDataChange(bank, e){
  61. RenderBankToEl(this, bank);
  62. }
  63. function ConnectElementToBank(el, bank){
  64. bank.listen("data_changed", HANDLE_BankDataChange.bind(el, bank));
  65. }
  66. function CreateBankDOMEntry(name, bank){
  67. var baseel = document.querySelector("." + BLI_TEMPLATE);
  68. if (!baseel){
  69. console.log("WARNING: Failed to find bank list item template.");
  70. return null;
  71. }
  72. var el = baseel.cloneNode(true);
  73. el.classList.remove(BLI_TEMPLATE);
  74. el.classList.remove("hidden");
  75. el.setAttribute("bankname", name);
  76. ConnectElementToBank(el, bank);
  77. el.addEventListener("click", HANDLE_BankClick);
  78. baseel.parentNode.appendChild(el);
  79. setTimeout(()=>{
  80. RenderBankToEl(el, bank);
  81. }, 500); // Make the render call in about a half second. Allow DOM time to catch up?
  82. return el;
  83. }
  84. class CTRLBanksStore{
  85. constructor(){
  86. var HANDLE_ChangeSurface = function(surf){
  87. if (!(surf instanceof NESBank)){
  88. if (CurrentBank !== ""){
  89. Banks[CurrentBank].el.classList.remove(BLI_SELECTED);
  90. CurrentBank = "";
  91. }
  92. } else {
  93. if (Banks.length <= 0 || (CurrentBank !== "" && Banks[CurrentBank].bank !== surf)){
  94. console.log("WARNING: Bank object being set outside of Bank Store.");
  95. }
  96. }
  97. }
  98. GlobalEvents.listen("change_surface", HANDLE_ChangeSurface);
  99. GlobalEvents.listen("bankstore-add", (function(e){
  100. if (e.hasOwnProperty("bankname")){
  101. this.createBank(e.bankname);
  102. this.activateBank(e.bankname);
  103. }
  104. }).bind(this));
  105. GlobalEvents.listen("bankstore-remove", (function(e){
  106. if (CurrentBank !== "")
  107. this.removeBank(CurrentBank);
  108. }).bind(this));
  109. }
  110. get length(){
  111. return Object.keys(Banks).length;
  112. }
  113. get obj(){
  114. var data = [];
  115. Object.keys(Banks).forEach((key) => {
  116. if (Banks.hasOwnProperty(key)){
  117. data.push({name:key, data:Banks[key].bank.base64});
  118. }
  119. });
  120. return data;
  121. }
  122. set obj(d){
  123. try {
  124. this.json = JSON.stringify(d);
  125. } catch (e) {
  126. throw e;
  127. }
  128. /*if (!(d instanceof Array))
  129. throw new TypeError("Expected Array object.");
  130. this.clear();
  131. d.forEach((item) => {
  132. if (typeof(item) === typeof({})){
  133. if ((name in item) && (data in item)){
  134. this.createBank(item.name, item.data);
  135. } else {
  136. console.log("WARNING: Bank object missing required properties. Skipped.");
  137. }
  138. }
  139. });*/
  140. }
  141. get json(){
  142. return JSON.stringify(this.obj);
  143. }
  144. set json(j){
  145. var validator = null;
  146. try {
  147. validator = JSONSchema.getValidator("BanksStoreSchema.json");
  148. } catch (e) {
  149. throw e;
  150. }
  151. if (validator(j)){
  152. this.clear();
  153. var o = JSON.parse(j);
  154. o.forEach((item) => {
  155. this.createBank(item.name, item.data);
  156. });
  157. } else {
  158. throw new Error("JSON Object validation failed.");
  159. }
  160. }
  161. initialize(){
  162. if (this.length <= 0){
  163. this.createBank("Bank");
  164. }
  165. return this;
  166. }
  167. createBank(name, bbase64){
  168. if (!(name in Banks)){
  169. var bank = new NESBank();
  170. if (typeof(bbase64) === "string"){
  171. try {
  172. bank.base64 = bbase64;
  173. } catch (e) {
  174. console.log("Failed to create Bank. " + e.toString());
  175. bank = null;
  176. }
  177. }
  178. if (bank !== null){
  179. var el = CreateBankDOMEntry(name, bank);
  180. if (el){
  181. var elname = SetElBankName(el, name);
  182. Banks[name] = {bank:bank, el:el, elname:elname};
  183. if (this.length <= 1){
  184. Banks[name].el.click();
  185. }
  186. }
  187. }
  188. }
  189. return this;
  190. }
  191. removeBank(name){
  192. if (name in Banks){
  193. if (name === CurrentBank){
  194. var keys = Object.keys(Banks);
  195. if (keys.length > 1){
  196. CurrentBank = (keys[0] !== name) ? keys[0] : keys[1];
  197. } else {
  198. CurrentBank = "";
  199. }
  200. }
  201. Banks[name].el.parentNode.removeChild(Banks[name].el);
  202. delete Banks[name];
  203. if (CurrentBank !== ""){
  204. Banks[CurrentBank].el.click();
  205. } else {
  206. GlobalEvents.emit("change_surface", null);
  207. }
  208. }
  209. return this;
  210. }
  211. renameBank(name, newname){
  212. if ((name in Banks) && !(newname in Banks)){
  213. Banks[newname] = Banks[name];
  214. Banks[newname].elname.value = newname;
  215. delete Banks[name];
  216. }
  217. return this;
  218. }
  219. activateBank(name){
  220. if (CurrentBank !== name && (name in Banks)){
  221. Banks[name].el.click();
  222. }
  223. return this;
  224. }
  225. clear(){
  226. Object.keys(Banks).forEach((item) => {
  227. item.el.parentNode.removeChild(item.el);
  228. });
  229. Banks = {};
  230. if (CurrentBank !== "")
  231. GlobalEvents.emit("change_surface", null);
  232. CurrentBank = "";
  233. }
  234. }
  235. const instance = new CTRLBanksStore();
  236. export default instance;