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文字以内のものにしてください。

307 行
7.2KB

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