@pictogrammers/components 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/README.md +11 -12
  2. package/favicon.svg +20 -0
  3. package/index.html +67 -49
  4. package/main.js +2 -0
  5. package/main.js.LICENSE.txt +10 -0
  6. package/package.json +6 -6
  7. package/pg/annoy/README.md +0 -5
  8. package/pg/annoy/__examples__/basic/basic.css +3 -0
  9. package/pg/annoy/__examples__/basic/basic.html +4 -7
  10. package/pg/annoy/__examples__/basic/basic.ts +3 -1
  11. package/pg/annoy/annoy.css +29 -198
  12. package/pg/annoy/annoy.html +5 -56
  13. package/pg/annoy/annoy.ts +4 -25
  14. package/pg/app/README.md +11 -0
  15. package/pg/app/__examples__/basic/basic.css +8 -0
  16. package/pg/app/__examples__/basic/basic.html +15 -0
  17. package/pg/app/__examples__/basic/basic.ts +13 -0
  18. package/pg/app/app.css +108 -0
  19. package/pg/app/app.html +16 -0
  20. package/pg/app/app.ts +45 -0
  21. package/pg/app/index.ts +3 -0
  22. package/pg/avatar/__examples__/basic/basic.ts +5 -3
  23. package/pg/button/README.md +3 -0
  24. package/pg/button/button.css +13 -12
  25. package/pg/button/button.spec.ts +35 -0
  26. package/pg/button/button.ts +17 -12
  27. package/pg/buttonLink/buttonLink.css +3 -2
  28. package/pg/buttonMenu/README.md +54 -0
  29. package/pg/buttonMenu/__examples__/basic/basic.html +6 -0
  30. package/pg/buttonMenu/__examples__/basic/basic.ts +43 -0
  31. package/pg/buttonMenu/buttonMenu.css +12 -0
  32. package/pg/buttonMenu/buttonMenu.html +4 -0
  33. package/pg/buttonMenu/buttonMenu.ts +63 -0
  34. package/pg/buttonMenu/index.ts +3 -0
  35. package/pg/buttonToggle/__examples__/basic/basic.ts +2 -2
  36. package/pg/buttonToggle/__examples__/persist/persist.html +10 -0
  37. package/pg/buttonToggle/__examples__/persist/persist.ts +35 -0
  38. package/pg/cardUser/__examples__/basic/basic.ts +0 -1
  39. package/pg/cardUser/cardUser.css +2 -10
  40. package/pg/cardUser/cardUser.html +0 -5
  41. package/pg/cardUser/cardUser.ts +0 -6
  42. package/pg/database/README.md +1 -1
  43. package/pg/database/__examples__/basic/basic.html +2 -1
  44. package/pg/database/__examples__/basic/basic.ts +3 -3
  45. package/pg/dropdown/dropdown.ts +0 -19
  46. package/pg/grid/__examples__/basic/basic.html +2 -2
  47. package/pg/grid/__examples__/basic/basic.ts +3 -2
  48. package/pg/grid/grid.ts +0 -1
  49. package/pg/icon/README.md +6 -5
  50. package/pg/icon/__examples__/basic/basic.html +2 -2
  51. package/pg/icon/__examples__/basic/basic.ts +1 -1
  52. package/pg/icon/icon.ts +6 -0
  53. package/pg/inputCheckList/__examples__/basic/basic.ts +5 -5
  54. package/pg/inputCheckList/inputCheckList.ts +2 -0
  55. package/pg/inputFileLocal/inputFileLocal.css +3 -2
  56. package/pg/inputPixelEditor/README.md +132 -0
  57. package/pg/inputPixelEditor/__examples__/basic/basic.css +29 -0
  58. package/pg/inputPixelEditor/__examples__/basic/basic.html +63 -0
  59. package/pg/inputPixelEditor/__examples__/basic/basic.ts +200 -0
  60. package/pg/inputPixelEditor/__examples__/basic/openUtils.ts +41 -0
  61. package/pg/inputPixelEditor/__examples__/basic/saveUtil.ts +35 -0
  62. package/pg/inputPixelEditor/index.ts +3 -0
  63. package/pg/inputPixelEditor/inputPixelEditor.css +27 -0
  64. package/pg/inputPixelEditor/inputPixelEditor.html +3 -0
  65. package/pg/inputPixelEditor/inputPixelEditor.ts +839 -0
  66. package/pg/inputPixelEditor/utils/bitmapToMask.ts +202 -0
  67. package/pg/inputPixelEditor/utils/cloneGrid.ts +17 -0
  68. package/pg/inputPixelEditor/utils/constants.ts +1 -0
  69. package/pg/inputPixelEditor/utils/createLayer.ts +8 -0
  70. package/pg/inputPixelEditor/utils/debounce.ts +5 -0
  71. package/pg/inputPixelEditor/utils/diffGrid.ts +26 -0
  72. package/pg/inputPixelEditor/utils/fillGrid.ts +11 -0
  73. package/pg/inputPixelEditor/utils/getEllipseOutlinePixels.ts +105 -0
  74. package/pg/inputPixelEditor/utils/getEllipsePixels.ts +28 -0
  75. package/pg/inputPixelEditor/utils/getGuides.ts +232 -0
  76. package/pg/inputPixelEditor/utils/getLinePixels.ts +18 -0
  77. package/pg/inputPixelEditor/utils/getRectangleOutlinePixels.ts +20 -0
  78. package/pg/inputPixelEditor/utils/getRectanglePixels.ts +15 -0
  79. package/pg/inputPixelEditor/utils/inputMode.ts +8 -0
  80. package/pg/inputPixelEditor/utils/interateGrid.ts +7 -0
  81. package/pg/inputPixelEditor/utils/isEmptyGrid.ts +3 -0
  82. package/pg/inputPixelEditor/utils/maskToBitmap.ts +66 -0
  83. package/pg/inputRange/__examples__/basic/basic.ts +4 -4
  84. package/pg/inputRange/inputRange.ts +6 -4
  85. package/pg/inputSelect/README.md +1 -1
  86. package/pg/inputSelect/__examples__/basic/basic.ts +7 -5
  87. package/pg/inputSelect/inputSelect.css +15 -12
  88. package/pg/inputSelect/inputSelect.html +3 -3
  89. package/pg/inputSelect/inputSelect.ts +33 -30
  90. package/pg/inputText/__examples__/basic/basic.ts +6 -6
  91. package/pg/inputText/inputText.css +1 -0
  92. package/pg/inputUserSelect/README.md +1 -1
  93. package/pg/inputUserSelect/inputUserSelect.ts +1 -1
  94. package/pg/listTag/__examples__/basic/basic.ts +4 -5
  95. package/pg/markdown/README.md +17 -3
  96. package/pg/markdown/__examples__/basic/basic.ts +2 -2
  97. package/pg/markdown/__examples__/basic/constants.ts +1 -1
  98. package/pg/markdown/markdown.css +11 -0
  99. package/pg/menu/README.md +46 -0
  100. package/pg/menu/__examples__/basic/basic.html +6 -0
  101. package/pg/menu/__examples__/basic/basic.ts +46 -0
  102. package/pg/menu/index.ts +3 -0
  103. package/pg/menu/menu.css +19 -0
  104. package/pg/menu/menu.html +1 -0
  105. package/pg/menu/menu.ts +119 -0
  106. package/pg/menuDivider/README.md +7 -0
  107. package/pg/menuDivider/__examples__/basic/basic.html +3 -0
  108. package/pg/menuDivider/__examples__/basic/basic.ts +28 -0
  109. package/pg/menuDivider/index.ts +3 -0
  110. package/pg/menuDivider/menuDivider.css +9 -0
  111. package/pg/menuDivider/menuDivider.html +1 -0
  112. package/pg/menuDivider/menuDivider.ts +22 -0
  113. package/pg/menuIcon/menuIcon.ts +43 -36
  114. package/pg/menuItem/README.md +32 -0
  115. package/pg/menuItem/__examples__/basic/basic.html +26 -0
  116. package/pg/menuItem/__examples__/basic/basic.ts +41 -0
  117. package/pg/menuItem/index.ts +3 -0
  118. package/pg/menuItem/menuItem.css +97 -0
  119. package/pg/menuItem/menuItem.html +1 -0
  120. package/pg/menuItem/menuItem.ts +77 -0
  121. package/pg/menuItemIcon/README.md +32 -0
  122. package/pg/menuItemIcon/__examples__/basic/basic.html +34 -0
  123. package/pg/menuItemIcon/__examples__/basic/basic.ts +55 -0
  124. package/pg/menuItemIcon/index.ts +3 -0
  125. package/pg/menuItemIcon/menuItemIcon.css +106 -0
  126. package/pg/menuItemIcon/menuItemIcon.html +4 -0
  127. package/pg/menuItemIcon/menuItemIcon.ts +156 -0
  128. package/pg/modalAlert/__examples__/basic/basic.ts +1 -1
  129. package/pg/modalAlert/modalAlert.css +1 -4
  130. package/pg/modalAlert/modalAlert.ts +18 -4
  131. package/pg/modification/__examples__/basic/basic.ts +1 -2
  132. package/pg/modification/__examples__/basic/constants.ts +25 -50
  133. package/pg/modification/modification.ts +1 -1
  134. package/pg/overlay/overlay.ts +13 -12
  135. package/pg/overlayContextMenu/README.md +35 -0
  136. package/pg/overlayContextMenu/__examples__/basic/basic.css +23 -0
  137. package/pg/overlayContextMenu/__examples__/basic/basic.html +7 -0
  138. package/pg/overlayContextMenu/__examples__/basic/basic.ts +87 -0
  139. package/pg/overlayContextMenu/index.ts +3 -0
  140. package/pg/overlayContextMenu/overlayContextMenu.css +16 -0
  141. package/pg/overlayContextMenu/overlayContextMenu.html +3 -0
  142. package/pg/overlayContextMenu/overlayContextMenu.ts +98 -0
  143. package/pg/overlayMenu/README.md +33 -0
  144. package/pg/overlayMenu/__examples__/basic/basic.css +3 -0
  145. package/pg/overlayMenu/__examples__/basic/basic.html +5 -0
  146. package/pg/overlayMenu/__examples__/basic/basic.ts +62 -0
  147. package/pg/overlayMenu/index.ts +3 -0
  148. package/pg/overlayMenu/overlayMenu.css +16 -0
  149. package/pg/overlayMenu/overlayMenu.html +3 -0
  150. package/pg/overlayMenu/overlayMenu.ts +67 -0
  151. package/pg/overlaySelectMenu/README.md +33 -0
  152. package/pg/overlaySelectMenu/__examples__/basic/basic.css +3 -0
  153. package/pg/overlaySelectMenu/__examples__/basic/basic.html +5 -0
  154. package/pg/overlaySelectMenu/__examples__/basic/basic.ts +62 -0
  155. package/pg/overlaySelectMenu/index.ts +3 -0
  156. package/pg/overlaySelectMenu/overlaySelectMenu.css +17 -0
  157. package/pg/overlaySelectMenu/overlaySelectMenu.html +3 -0
  158. package/pg/overlaySelectMenu/overlaySelectMenu.ts +96 -0
  159. package/pg/overlaySubMenu/README.md +35 -0
  160. package/pg/overlaySubMenu/index.ts +3 -0
  161. package/pg/overlaySubMenu/overlaySubMenu.css +27 -0
  162. package/pg/overlaySubMenu/overlaySubMenu.html +3 -0
  163. package/pg/overlaySubMenu/overlaySubMenu.ts +103 -0
  164. package/pg/picker/picker.ts +1 -19
  165. package/pg/scroll/__examples__/basic/basic.ts +1 -1
  166. package/pg/search/__examples__/basic/basic.ts +10 -7
  167. package/pg/search/search.css +2 -2
  168. package/pg/shared/models/user.ts +0 -2
  169. package/pg/tab/tab.ts +0 -10
  170. package/pg/tabs/partials/tab.css +42 -0
  171. package/pg/tabs/partials/tab.ts +70 -0
  172. package/pg/tabs/tabs.css +0 -53
  173. package/pg/tabs/tabs.ts +54 -70
  174. package/pg/toast/README.md +35 -6
  175. package/pg/toast/__examples__/basic/basic.html +7 -0
  176. package/pg/toast/__examples__/basic/basic.ts +76 -0
  177. package/pg/toast/toast.css +3 -0
  178. package/pg/toast/toast.ts +20 -4
  179. package/pg/tooltip/addTooltip.ts +3 -1
  180. package/pg/tooltip/tooltip.ts +1 -1
  181. package/pg/tree/README.md +67 -0
  182. package/pg/tree/__examples__/basic/basic.html +10 -0
  183. package/pg/tree/__examples__/basic/basic.ts +162 -0
  184. package/pg/tree/index.ts +3 -0
  185. package/pg/tree/tree.css +28 -0
  186. package/pg/tree/tree.html +1 -0
  187. package/pg/tree/tree.ts +217 -0
  188. package/pg/treeButtonIcon/README.md +39 -0
  189. package/pg/treeButtonIcon/index.ts +3 -0
  190. package/pg/treeButtonIcon/treeButtonIcon.css +18 -0
  191. package/pg/treeButtonIcon/treeButtonIcon.html +3 -0
  192. package/pg/treeButtonIcon/treeButtonIcon.ts +42 -0
  193. package/pg/treeItem/README.md +3 -0
  194. package/pg/treeItem/index.ts +3 -0
  195. package/pg/treeItem/treeItem.css +263 -0
  196. package/pg/treeItem/treeItem.html +16 -0
  197. package/pg/treeItem/treeItem.ts +558 -0
  198. package/pgAnnoy.js +1 -0
  199. package/pgApp.js +1 -0
  200. package/pgAvatar.js +1 -0
  201. package/pgButton.js +1 -0
  202. package/pgButtonGroup.js +1 -0
  203. package/pgButtonLink.js +1 -0
  204. package/pgButtonMenu.js +1 -0
  205. package/pgButtonToggle.js +1 -0
  206. package/pgCard.js +1 -0
  207. package/pgCardUser.js +1 -0
  208. package/pgColor.js +1 -0
  209. package/pgDatabase.js +1 -0
  210. package/pgDropdown.js +1 -0
  211. package/pgGrid.js +1 -0
  212. package/pgHeader.js +1 -0
  213. package/pgIcon.js +1 -0
  214. package/pgInputCheck.js +1 -0
  215. package/pgInputCheckList.js +1 -0
  216. package/pgInputFileLocal.js +1 -0
  217. package/pgInputHexRgb.js +1 -0
  218. package/pgInputPixelEditor.js +1 -0
  219. package/pgInputRange.js +1 -0
  220. package/pgInputSelect.js +1 -0
  221. package/pgInputText.js +1 -0
  222. package/pgInputTextIcon.js +1 -0
  223. package/pgInputUserSelect.js +1 -0
  224. package/pgListTag.js +1 -0
  225. package/pgMarkdown.js +2 -0
  226. package/pgMarkdown.js.LICENSE.txt +10 -0
  227. package/pgMenu.js +1 -0
  228. package/pgMenuDivider.js +1 -0
  229. package/pgMenuIcon.js +1 -0
  230. package/pgMenuItem.js +1 -0
  231. package/pgMenuItemIcon.js +1 -0
  232. package/pgModalAlert.js +1 -0
  233. package/pgModification.js +1 -0
  234. package/pgNav.js +1 -0
  235. package/pgOverlay.js +1 -0
  236. package/pgOverlayContextMenu.js +1 -0
  237. package/pgOverlayMenu.js +1 -0
  238. package/pgOverlaySelectMenu.js +1 -0
  239. package/pgOverlaySubMenu.js +1 -0
  240. package/pgPicker.js +1 -0
  241. package/pgPreview.js +1 -0
  242. package/pgScroll.js +1 -0
  243. package/pgSearch.js +1 -0
  244. package/pgTab.js +1 -0
  245. package/pgTabs.js +1 -0
  246. package/pgToast.js +1 -0
  247. package/pgToasts.js +1 -0
  248. package/pgTooltip.js +1 -0
  249. package/pgTree.js +1 -0
  250. package/pgTreeButtonIcon.js +1 -0
  251. package/pgTreeItem.js +1 -0
  252. package/theme-ui3.css +31 -0
  253. package/@types/css.d.ts +0 -4
  254. package/@types/html.d.ts +0 -4
  255. package/dist/main.js +0 -3819
  256. package/dist/pgAnnoy.js +0 -116
  257. package/dist/pgAvatar.js +0 -136
  258. package/dist/pgButton.js +0 -116
  259. package/dist/pgButtonGroup.js +0 -116
  260. package/dist/pgButtonLink.js +0 -116
  261. package/dist/pgButtonToggle.js +0 -146
  262. package/dist/pgCard.js +0 -116
  263. package/dist/pgCardUser.js +0 -196
  264. package/dist/pgColor.js +0 -136
  265. package/dist/pgDatabase.js +0 -236
  266. package/dist/pgDropdown.js +0 -686
  267. package/dist/pgGrid.js +0 -126
  268. package/dist/pgHeader.js +0 -116
  269. package/dist/pgIcon.js +0 -116
  270. package/dist/pgInputCheck.js +0 -116
  271. package/dist/pgInputCheckList.js +0 -126
  272. package/dist/pgInputFileLocal.js +0 -116
  273. package/dist/pgInputHexRgb.js +0 -126
  274. package/dist/pgInputRange.js +0 -116
  275. package/dist/pgInputSelect.js +0 -116
  276. package/dist/pgInputText.js +0 -116
  277. package/dist/pgInputTextIcon.js +0 -176
  278. package/dist/pgInputUserSelect.js +0 -116
  279. package/dist/pgListTag.js +0 -136
  280. package/dist/pgMarkdown.js +0 -346
  281. package/dist/pgMenuIcon.js +0 -206
  282. package/dist/pgModalAlert.js +0 -126
  283. package/dist/pgModification.js +0 -396
  284. package/dist/pgNav.js +0 -116
  285. package/dist/pgOverlay.js +0 -96
  286. package/dist/pgPicker.js +0 -116
  287. package/dist/pgPreview.js +0 -116
  288. package/dist/pgScroll.js +0 -266
  289. package/dist/pgSearch.js +0 -146
  290. package/dist/pgTab.js +0 -116
  291. package/dist/pgTabs.js +0 -136
  292. package/dist/pgToast.js +0 -136
  293. package/dist/pgToasts.js +0 -136
  294. package/dist/pgTooltip.js +0 -126
@@ -0,0 +1,839 @@
1
+ import { Component, Prop, Part, normalizeInt, normalizeBoolean } from '@pictogrammers/element';
2
+
3
+ import template from './inputPixelEditor.html';
4
+ import style from './inputPixelEditor.css';
5
+ import { InputMode } from './utils/inputMode';
6
+ import cloneGrid from './utils/cloneGrid';
7
+ import getEllipseOutlinePixels from './utils/getEllipseOutlinePixels';
8
+ import { WHITE } from './utils/constants';
9
+ import getLinePixels from './utils/getLinePixels';
10
+ import getRectanglePixels from './utils/getRectanglePixels';
11
+ import getRectangleOutlinePixels from './utils/getRectangleOutlinePixels';
12
+ import fillGrid from './utils/fillGrid';
13
+ import iterateGrid from './utils/interateGrid';
14
+ import bitmaskToPath from './utils/bitmapToMask';
15
+ import createLayer from './utils/createLayer';
16
+ import diffGrid from './utils/diffGrid';
17
+ import { getGuides } from './utils/getGuides';
18
+
19
+ type Pixel = { x: number, y: number };
20
+
21
+ type Color = [number, number, number, number];
22
+
23
+ enum HistoryType {
24
+ Group,
25
+ Pixel,
26
+ ColorUpdate,
27
+ ColorAdd,
28
+ ColorRemove,
29
+ LayerAdd,
30
+ LayerRemove,
31
+ LayerName,
32
+ LayerLock,
33
+ LayerUnlock,
34
+ LayerExport,
35
+ LayerVisible,
36
+ LayerOpacity
37
+ }
38
+
39
+ type HistoryGroupType = {
40
+ name: string
41
+ }
42
+
43
+ type HistoryPixelType = {
44
+ pixels: [number, number, number][],
45
+ layer: number
46
+ }
47
+
48
+ type HistoryColorUpdateType = {
49
+ color: Color,
50
+ index: number
51
+ }
52
+
53
+ type History = {
54
+ type: HistoryType,
55
+ data: HistoryGroupType | HistoryPixelType | HistoryColorUpdateType
56
+ }
57
+
58
+ type Layer = {
59
+ name: string,
60
+ visible: boolean,
61
+ locked: boolean,
62
+ opacity: number,
63
+ export: boolean
64
+ }
65
+
66
+ interface FileOptions {
67
+ history?: boolean
68
+ }
69
+
70
+ interface File {
71
+ width: number
72
+ height: number
73
+ transparent: boolean
74
+ colors: Color[]
75
+ layers: Layer[]
76
+ data: number[][][]
77
+ undo?: History[]
78
+ redo?: History[]
79
+ }
80
+
81
+ function toColor([r, g, b, a]: Color) {
82
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
83
+ }
84
+
85
+ @Component({
86
+ selector: 'pg-input-pixel-editor',
87
+ style,
88
+ template
89
+ })
90
+ export default class PgInputPixelEditor extends HTMLElement {
91
+ @Prop(normalizeInt) width: number = 10;
92
+ @Prop(normalizeInt) height: number = 10;
93
+ @Prop(normalizeInt) size: number = 10;
94
+ @Prop(normalizeInt) gridSize: number = 1;
95
+ @Prop(normalizeBoolean) transparent: boolean = false;
96
+ @Prop() placeholder: string = '';
97
+
98
+ @Part() $canvas: HTMLCanvasElement;
99
+
100
+ // Internal State
101
+ #inputMode: InputMode = InputMode.Pixel;
102
+ #isPressed: boolean = false;
103
+ #isEditing: boolean = false;
104
+ #startColor: number = -1;
105
+ #startX: number = -1;
106
+ #startY: number = -1;
107
+ #x: number = -1;
108
+ #y: number = -1;
109
+ #layer: number = 0;
110
+ #layers: Layer[] = [];
111
+ #isCtrl: boolean = false;
112
+ #isShift: boolean = false;
113
+ #isAlt: boolean = false;
114
+ #data: number[][][] = [];
115
+ #undoHistory: History[] = [];
116
+ #redoHistory: History[] = [];
117
+ #context: CanvasRenderingContext2D;
118
+ #colors: Color[] = [
119
+ [0, 0, 0, 0],
120
+ [0, 0, 0, 1]
121
+ ];
122
+ #baseLayer: HTMLCanvasElement;
123
+ #baseLayerContext: CanvasRenderingContext2D;
124
+ #editLayer: HTMLCanvasElement;
125
+ #editLayerContext: CanvasRenderingContext2D;
126
+ #noEditLayer: HTMLCanvasElement;
127
+ #noEditLayerContext: CanvasRenderingContext2D;
128
+ #previewLayer: HTMLCanvasElement;
129
+ #previewLayerContext: CanvasRenderingContext2D;
130
+
131
+ connectedCallback() {
132
+ // Init
133
+ const context = this.$canvas.getContext('2d');
134
+ if (context === null) { return; }
135
+ this.#context = context;
136
+ // Wire Up Events
137
+ this.$canvas.addEventListener(
138
+ 'contextmenu',
139
+ this.handleContextMenu.bind(this)
140
+ );
141
+ this.$canvas.addEventListener(
142
+ 'doubleclick',
143
+ this.handleDoubleClick.bind(this)
144
+ );
145
+ this.$canvas.addEventListener(
146
+ 'pointerdown',
147
+ this.handlePointerDown.bind(this)
148
+ );
149
+ this.$canvas.addEventListener(
150
+ 'pointerup',
151
+ this.handlePointerUp.bind(this)
152
+ );
153
+ this.$canvas.addEventListener(
154
+ 'pointermove',
155
+ this.handlePointerMove.bind(this)
156
+ );
157
+ this.$canvas.addEventListener(
158
+ 'pointerenter',
159
+ this.handlePointerEnter.bind(this)
160
+ );
161
+ this.$canvas.addEventListener(
162
+ 'pointerleave',
163
+ this.handlePointerLeave.bind(this)
164
+ );
165
+ this.$canvas.addEventListener(
166
+ 'keydown',
167
+ this.handleKeyDown.bind(this)
168
+ );
169
+ this.$canvas.addEventListener(
170
+ 'keyup',
171
+ this.handleKeyUp.bind(this)
172
+ );
173
+ }
174
+
175
+ render(changes) {
176
+ if (changes.width || changes.height || changes.size || changes.transparent) {
177
+ this.#init();
178
+ }
179
+ }
180
+
181
+ #reset = true;
182
+ #init() {
183
+ const totalSize = this.size + this.gridSize;
184
+ const actualWidth = this.width * totalSize - this.gridSize;
185
+ const actualHeight = this.height * totalSize - this.gridSize;
186
+ this.$canvas.width = actualWidth;
187
+ this.$canvas.height = actualHeight;
188
+ this.#context.clearRect(0, 0, actualWidth, actualHeight);
189
+ [this.#baseLayer, this.#baseLayerContext] = createLayer(actualWidth, actualHeight);
190
+ [this.#editLayer, this.#editLayerContext] = createLayer(actualWidth, actualHeight);
191
+ [this.#noEditLayer, this.#noEditLayerContext] = createLayer(actualWidth, actualHeight);
192
+ [this.#previewLayer, this.#previewLayerContext] = createLayer(actualWidth, actualHeight);
193
+ if (this.transparent) {
194
+ for (let y = 0; y < this.height; y++) {
195
+ for (let x = 0; x < this.width; x++) {
196
+ this.#baseLayerContext.fillStyle = WHITE;
197
+ this.#baseLayerContext.fillRect(x * totalSize, y * totalSize, this.size + 1, this.size + 1);
198
+ this.#baseLayerContext.fillStyle = '#DDD';
199
+ this.#baseLayerContext.fillRect(x * totalSize + Math.ceil(this.size / 2), y * totalSize, Math.floor(this.size / 2), Math.floor(this.size / 2));
200
+ this.#baseLayerContext.fillRect(x * totalSize, y * totalSize + Math.floor(this.size / 2), Math.ceil(this.size / 2), Math.ceil(this.size / 2));
201
+ }
202
+ }
203
+ } else {
204
+ this.#baseLayerContext.clearRect(0, 0, actualWidth, actualHeight);
205
+ }
206
+ if (this.#reset) {
207
+ this.#layer = 0;
208
+ this.#layers = [{
209
+ name: 'Layer 1',
210
+ export: true,
211
+ locked: false,
212
+ visible: true,
213
+ opacity: 1
214
+ }];
215
+ this.#data = [fillGrid(this.width, this.height)];
216
+ this.#reset = false;
217
+ this.#undoHistory = [];
218
+ this.#redoHistory = [];
219
+ } else {
220
+ this.#redraw();
221
+ }
222
+ this.#updateGrid();
223
+ }
224
+
225
+ #redraw() {
226
+ // Render individual pixels
227
+ const data = this.#data.toReversed();
228
+ const layerCount = data.length;
229
+ for (let y = 0; y < this.height; y++) {
230
+ if (y >= data[0].length) {
231
+ for (let l = 0; l < layerCount; l++) {
232
+ data[l].push(new Array(this.width).fill(0));
233
+ }
234
+ }
235
+ for (let x = 0; x < this.width; x++) {
236
+ if (x >= data[0][y].length) {
237
+ for (let l = 0; l < layerCount; l++) {
238
+ data[l][y].push(0);
239
+ }
240
+ }
241
+ for (let l = 0; l < layerCount; l++) {
242
+ if (data[l][y][x] !== 0) {
243
+ this.#setPixel(x, y, data[l][y][x]);
244
+ break;
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ #handleChange() {
252
+ const paths = this.#data.map(layer => bitmaskToPath(layer, { scale: 1 }));
253
+ console.log('change:', paths);
254
+ this.dispatchEvent(new CustomEvent('change', {
255
+ detail: { value: paths }
256
+ }));
257
+ /*this.dispatchEvent(new CustomEvent('change', {
258
+ detail: data
259
+ }));*/
260
+ };
261
+
262
+ #delayTimerId: number = 0;
263
+ #delayedChange() {
264
+ clearInterval(this.#delayTimerId);
265
+ this.#delayTimerId = window.setTimeout(this.#handleChange.bind(this), 1000);
266
+ };
267
+
268
+ #setPixel(x: number, y: number, color: number) {
269
+ if (x > this.width) {
270
+ throw new Error(`Invalid x; ${x} > ${this.width}`);
271
+ }
272
+ if (y > this.height) {
273
+ throw new Error(`Invalid y; ${y} > ${this.height}`);
274
+ }
275
+ const totalSize = this.size + this.gridSize;
276
+ // Edit Layer
277
+ this.#context.clearRect(x * totalSize, y * totalSize, this.size, this.size);
278
+ this.#editLayerContext.clearRect(x * totalSize, y * totalSize, this.size, this.size);
279
+ this.#noEditLayerContext.clearRect(x * totalSize, y * totalSize, this.size, this.size);
280
+ // Edit layer
281
+ if (this.#colors[color][3] !== 0) {
282
+ this.#editLayerContext.fillStyle = WHITE;
283
+ this.#editLayerContext.fillRect(
284
+ x * totalSize - (this.gridSize) + 1,
285
+ y * totalSize - (this.gridSize) + 1,
286
+ this.size + (this.gridSize * 2) - 2,
287
+ this.size + (this.gridSize * 2) - 2
288
+ );
289
+ this.#editLayerContext.fillStyle = toColor(this.#colors[color]);
290
+ this.#editLayerContext.fillRect(x * totalSize + 1, y * totalSize + 1, this.size - 2, this.size - 2);
291
+ }
292
+ // No Edit layer
293
+ if (this.#colors[color][3] !== 0) {
294
+ this.#noEditLayerContext.fillStyle = toColor(this.#colors[color]);
295
+ this.#noEditLayerContext.fillRect(x * totalSize, y * totalSize, this.size, this.size);
296
+ }
297
+ // base layer to main canvas
298
+ this.#context.drawImage(
299
+ this.#baseLayer,
300
+ x * totalSize, y * totalSize, this.size + 2, this.size + 2,
301
+ x * totalSize, y * totalSize, this.size + 2, this.size + 2
302
+ );
303
+ // editing layer to main canvas
304
+ this.#context.drawImage(
305
+ this.#isEditing ? this.#editLayer : this.#noEditLayer,
306
+ x * totalSize, y * totalSize, this.size + 2, this.size + 2,
307
+ x * totalSize, y * totalSize, this.size + 2, this.size + 2
308
+ );
309
+ console.log('draw pixel(x, y, color, data):', x, y, color, this.#data[this.#layer][y][x]);
310
+ // Verify this is the only place setting pixel data!
311
+ this.#data[this.#layer][y][x] = color;
312
+ this.#delayedChange();
313
+ }
314
+
315
+ #setPixelAll() {
316
+ for (let y = 0; y < this.height; y++) {
317
+ for (let x = 0; x < this.width; x++) {
318
+ this.#setPixel(x, y, this.#data[this.#layer][y][x]);
319
+ }
320
+ }
321
+ }
322
+
323
+ #setPreview(pixels: Pixel[], previousX: number, previousY: number) {
324
+ const totalSize = this.size + this.gridSize;
325
+ const actualWidth = this.width * totalSize - this.gridSize;
326
+ const actualHeight = this.height * totalSize - this.gridSize;
327
+ const { minX, maxX, minY, maxY } = pixels.reduce((previous, current) => {
328
+ return {
329
+ minX: Math.min(previous.minX, current.x, previousX),
330
+ maxX: Math.max(previous.maxX, current.x, previousX),
331
+ minY: Math.min(previous.minY, current.y, previousY),
332
+ maxY: Math.max(previous.maxY, current.y, previousY)
333
+ };
334
+ }, { minX: this.width, maxX: 0, minY: this.height, maxY: 0 });
335
+ const x = minX * totalSize;
336
+ const y = minY * totalSize;
337
+ const width = (maxX - minX + 1) * totalSize;
338
+ const height = (maxY - minY + 1) * totalSize;
339
+ this.#context.clearRect(x, y, width, height);
340
+ // base layer to main canvas
341
+ this.#context.drawImage(
342
+ this.#baseLayer,
343
+ x, y, width, height,
344
+ x, y, width, height
345
+ );
346
+ // edit to main canvas
347
+ this.#context.drawImage(
348
+ this.#editLayer,
349
+ x, y, width, height,
350
+ x, y, width, height
351
+ );
352
+ // preview layer
353
+ this.#previewLayerContext.clearRect(0, 0, actualWidth, actualHeight);
354
+ pixels.forEach(({ x, y }) => {
355
+ this.#previewLayerContext.fillStyle = WHITE;
356
+ this.#previewLayerContext.beginPath();
357
+ this.#previewLayerContext.arc(x * totalSize + 5, y * totalSize + 5, 3, 0, 2 * Math.PI);
358
+ this.#previewLayerContext.closePath();
359
+ this.#previewLayerContext.fill();
360
+ this.#previewLayerContext.fillStyle = '#1B79C8';
361
+ this.#previewLayerContext.beginPath();
362
+ this.#previewLayerContext.arc(x * totalSize + 5, y * totalSize + 5, 2, 0, 2 * Math.PI);
363
+ this.#previewLayerContext.closePath();
364
+ this.#previewLayerContext.fill();
365
+ });
366
+ // preview layer to main canvas
367
+ this.#context.drawImage(
368
+ this.#previewLayer,
369
+ x, y, width, height,
370
+ x, y, width, height
371
+ );
372
+ // Debug
373
+ this.dispatchEvent(new CustomEvent('debug', {
374
+ detail: {
375
+ x,
376
+ y,
377
+ width,
378
+ height,
379
+ canvas: this.$canvas,
380
+ context: this.#context,
381
+ editLayer: this.#editLayer,
382
+ noEditLayer: this.#noEditLayer,
383
+ baseLayer: this.#baseLayer,
384
+ previewLayer: this.#previewLayer
385
+ }
386
+ }));
387
+ }
388
+
389
+ handleKeyDown(event: KeyboardEvent) {
390
+ console.log(event.shiftKey, event.ctrlKey, event.altKey, event.key);
391
+ switch (event.key) {
392
+ case ' ':
393
+ console.log('space');
394
+ break;
395
+ case 'Escape':
396
+ console.log('escape');
397
+ // Cancel editing
398
+ break;
399
+ }
400
+ }
401
+
402
+ handleKeyUp(event: KeyboardEvent) {
403
+
404
+ }
405
+
406
+ handleContextMenu(event: MouseEvent) {
407
+ event?.preventDefault();
408
+ }
409
+
410
+ handleDoubleClick(event: MouseEvent) {
411
+ event?.preventDefault();
412
+ }
413
+
414
+ handlePointerDown(event: MouseEvent) {
415
+ if (event.buttons !== 1 && event.buttons !== 32) {
416
+ event.preventDefault();
417
+ event.stopPropagation();
418
+ return;
419
+ }
420
+ // Update Modifiers
421
+ this.#isAlt = event.altKey;
422
+ this.#isCtrl = event.ctrlKey;
423
+ this.#isShift = event.shiftKey;
424
+ // Drawing
425
+ const rect = this.$canvas.getBoundingClientRect();
426
+ const totalSize = this.size + this.gridSize;
427
+ let newX = Math.floor((event.clientX - rect.left) / totalSize);
428
+ let newY = Math.floor((event.clientY - rect.top) / totalSize);
429
+ if (newX === this.#x && newY === this.#y) { return; }
430
+ if (newX >= this.width) { newX = this.width - 1; }
431
+ if (newY >= this.height) { newY = this.height - 1; }
432
+ this.#isPressed = true;
433
+ this.#startColor = this.#data[this.#layer][newY][newX];
434
+ this.#startX = newX;
435
+ this.#startY = newY;
436
+ this.#x = newX;
437
+ this.#y = newY;
438
+ const color = event.buttons === 32 ? 0 : 1;
439
+ switch (this.#inputMode) {
440
+ case InputMode.Pixel:
441
+ this.#setPixel(newX, newY, color);
442
+ this.#data[this.#layer][newY][newX] = color;
443
+ break;
444
+ }
445
+ console.log(this.#inputMode, newX, newY);
446
+ }
447
+
448
+ handlePointerUp(event: MouseEvent) {
449
+ const rect = this.$canvas.getBoundingClientRect();
450
+ const totalSize = this.size + this.gridSize;
451
+ let newX = Math.floor((event.clientX - rect.left) / totalSize);
452
+ let newY = Math.floor((event.clientY - rect.top) / totalSize);
453
+ if (newX >= this.width) { newX = this.width - 1; }
454
+ if (newY >= this.height) { newY = this.height - 1; }
455
+ if (this.#startX === -1 && this.#startY === -1) {
456
+ return;
457
+ }
458
+ // Single Tap
459
+ if (newX === this.#startX && newY === this.#startY && this.#startColor === 1) {
460
+ switch (this.#inputMode) {
461
+ case InputMode.Pixel:
462
+ this.#setPixel(newX, newY, 0);
463
+ this.#data[this.#layer][newY][newX] = 0;
464
+ break;
465
+ }
466
+ } else {
467
+ switch (this.#inputMode) {
468
+ case InputMode.Line:
469
+ getLinePixels(this.#startX, this.#startY, newX, newY).forEach(({ x, y }) => {
470
+ this.#setPixel(x, y, 1);
471
+ });
472
+ break;
473
+ case InputMode.Rectangle:
474
+ getRectanglePixels(this.#startX, this.#startY, newX, newY).forEach(({ x, y }) => {
475
+ this.#setPixel(x, y, 1);
476
+ });
477
+ break;
478
+ case InputMode.RectangleOutline:
479
+ getRectangleOutlinePixels(this.#startX, this.#startY, newX, newY).forEach(({ x, y }) => {
480
+ this.#setPixel(x, y, 1);
481
+ });
482
+ break;
483
+ case InputMode.Ellipse:
484
+ getEllipseOutlinePixels(this.#startX, this.#startY, newX, newY).forEach(({ x, y }) => {
485
+ this.#setPixel(x, y, 1);
486
+ });
487
+ break;
488
+ case InputMode.EllipseOutline:
489
+ getEllipseOutlinePixels(this.#startX, this.#startY, newX, newY).forEach(({ x, y }) => {
490
+ this.#setPixel(x, y, 1);
491
+ });
492
+ break;
493
+ }
494
+ }
495
+ this.#x = -1;
496
+ this.#y = -1;
497
+ this.#isPressed = false;
498
+ }
499
+
500
+ handlePointerMove(event: PointerEvent) {
501
+ const canvas = this.$canvas;
502
+ if (this.#isPressed) {
503
+ this.#isAlt = event.altKey;
504
+ this.#isCtrl = event.ctrlKey;
505
+ this.#isShift = event.shiftKey;
506
+ const data = this.#data;
507
+ const rect = canvas.getBoundingClientRect();
508
+ const totalSize = this.size + this.gridSize;
509
+ const points: [number, number][] = [];
510
+ const startX = this.#startX;
511
+ const startY = this.#startY;
512
+ const x = this.#x;
513
+ const y = this.#y;
514
+ // If supported get all the inbetween points
515
+ // really noticable for pen support + pencil tool
516
+ if (typeof event.getCoalescedEvents === 'function') {
517
+ const events = event.getCoalescedEvents();
518
+ for (const evt of events) {
519
+ let tX = Math.floor((evt.clientX - rect.left) / totalSize);
520
+ let tY = Math.floor((evt.clientY - rect.top) / totalSize);
521
+ if (tX >= this.width || tY >= this.height || (tX === x && tY === y)) {
522
+ continue;
523
+ }
524
+ points.push([tX, tY]);
525
+ }
526
+ } else {
527
+ let newX = Math.floor((event.clientX - rect.left) / totalSize);
528
+ let newY = Math.floor((event.clientY - rect.top) / totalSize);
529
+ if (newX === x && newY === y) { return; }
530
+ if (newX >= this.width) { newX = this.width - 1; }
531
+ if (newY >= this.height) { newY = this.height - 1; }
532
+ points.push([newX, newY]);
533
+ }
534
+ // Is Eraser
535
+ const color = event.buttons === 32 ? 0 : 1;
536
+ // Shape tools only care about the last point
537
+ if (points.length === 0) { return; }
538
+ const [lastX, lastY] = points.at(-1) as [number, number];
539
+ // This is not ideal, but might be good enough,
540
+ // really it should be finding the point furthest absolute
541
+ // point from startX/startY.
542
+ this.#x = lastX;
543
+ this.#y = lastY;
544
+ switch (this.#inputMode) {
545
+ case InputMode.Pixel:
546
+ for (var point of points) {
547
+ this.#setPixel(point[0], point[1], color);
548
+ data[this.#layer][point[1]][point[0]] = color;
549
+ }
550
+ break;
551
+ case InputMode.Line:
552
+ this.#setPreview(getLinePixels(startX, startY, lastX, lastY), x, y);
553
+ break;
554
+ case InputMode.Rectangle:
555
+ this.#setPreview(getRectanglePixels(startX, startY, lastX, lastY), x, y);
556
+ break;
557
+ case InputMode.RectangleOutline:
558
+ this.#setPreview(getRectangleOutlinePixels(startX, startY, lastX, lastY), x, y);
559
+ break;
560
+ case InputMode.Ellipse:
561
+ this.#setPreview(getEllipseOutlinePixels(startX, startY, lastX, lastY), x, y);
562
+ break;
563
+ case InputMode.EllipseOutline:
564
+ this.#setPreview(getEllipseOutlinePixels(startX, startY, lastX, lastY), x, y);
565
+ break;
566
+ }
567
+ }
568
+ }
569
+
570
+ handlePointerEnter(event: MouseEvent) {
571
+ if (!this.#isPressed && !this.#isEditing) {
572
+ this.#isEditing = true;
573
+ // base layer to main canvas
574
+ this.#context.drawImage(this.#baseLayer, 0, 0);
575
+ // editing layer to main canvas
576
+ this.#context.drawImage(this.#isEditing ? this.#editLayer : this.#noEditLayer, 0, 0);
577
+ }
578
+ }
579
+
580
+ handlePointerLeave(event: MouseEvent) {
581
+ if (!this.#isPressed) {
582
+ this.#isEditing = false;
583
+ // base layer to main canvas
584
+ this.#context.drawImage(this.#baseLayer, 0, 0);
585
+ // editing layer to main canvas
586
+ this.#context.drawImage(this.#isEditing ? this.#editLayer : this.#noEditLayer, 0, 0);
587
+ }
588
+ }
589
+
590
+ #updateGrid() {
591
+ // base layer to main canvas
592
+ this.#context.drawImage(this.#baseLayer, 0, 0);
593
+ // editing layer to main canvas
594
+ this.#context.drawImage(this.#noEditLayer, 0, 0);
595
+ }
596
+
597
+ mergeColor(fromIndex: number, toIndex: number) {
598
+ // ToDo: Code this
599
+ }
600
+
601
+ clear() {
602
+ const gridEmpty = fillGrid(this.width, this.height);
603
+ const diff = diffGrid(this.#data[this.#layer], gridEmpty);
604
+ this.#undoHistory.push({
605
+ type: HistoryType.Group,
606
+ data: {
607
+ name: 'Clear'
608
+ }
609
+ });
610
+ this.#undoHistory.push({
611
+ type: HistoryType.Pixel,
612
+ data: {
613
+ pixels: [],
614
+ layer: this.#layer
615
+ }
616
+ });
617
+ this.#data = [fillGrid(this.width, this.height)];
618
+ this.#updateGrid();
619
+ }
620
+
621
+ reset() {
622
+ this.#reset = true;
623
+ this.#init();
624
+ }
625
+
626
+ clearHistory() {
627
+ this.#undoHistory = [];
628
+ this.#redoHistory = [];
629
+ }
630
+
631
+ applyTemplate(template: number[][]) {
632
+ this.#data = [template];
633
+ this.#setPixelAll();
634
+ }
635
+
636
+ flipHorizontal() {
637
+ const cloned = cloneGrid(this.#data[this.#layer]);
638
+ const w = cloned[0].length - 1;
639
+ iterateGrid(this.#data[this.#layer], (x, y) => {
640
+ cloned[y][x] = this.#data[this.#layer][y][w - x];
641
+ });
642
+ this.#data[this.#layer] = cloned;
643
+ }
644
+
645
+ flipVertical() {
646
+ const cloned = cloneGrid(this.#data[this.#layer]);
647
+ const h = cloned.length - 1;
648
+ iterateGrid(this.#data[this.#layer], (x, y) => {
649
+ cloned[this.#layer][y][x] = this.#data[h - y][x];
650
+ });
651
+ this.#data[this.#layer] = cloned;
652
+ }
653
+
654
+ move(translateX: number, translateY: number) {
655
+ const cloned = fillGrid(this.width, this.height);
656
+ for (let iy = 0; iy < this.height; iy++) {
657
+ cloned[iy].fill(0);
658
+ }
659
+ iterateGrid(this.#data[this.#layer], (x, y) => {
660
+ if (y - translateY < 0
661
+ || x - translateX < 0
662
+ || y - translateY >= this.height
663
+ || x - translateX >= this.width) {
664
+ return;
665
+ }
666
+ cloned[y][x] = this.#data[this.#layer][y - translateY][x - translateX];
667
+ });
668
+ this.#data[this.#layer] = cloned;
669
+ }
670
+
671
+ invert() {
672
+ // Only works with 2 colors
673
+ if (this.#colors.length > 2) {
674
+ return;
675
+ }
676
+ iterateGrid(this.#data[this.#layer], (x, y) => {
677
+ this.#data[this.#layer][y][x] = this.#data[this.#layer][y][x] === 0 ? 1 : 0;
678
+ });
679
+ this.#setPixelAll();
680
+ }
681
+
682
+ applyGuides() {
683
+ const guides = getGuides(this.width, this.height, this.size, this.gridSize);
684
+ this.#baseLayerContext.drawImage(guides, 0, 0);
685
+ }
686
+
687
+ clearGuides() {
688
+
689
+ }
690
+
691
+ undo() {
692
+ // ToDo: Rewrite to use new history api
693
+ const revert = this.#undoHistory.pop();
694
+ if (!revert) { return; }
695
+ switch (revert.type) {
696
+ case HistoryType.Pixel:
697
+ this.#redoHistory.push(revert);
698
+ (revert.data as HistoryPixelType).pixels.forEach((item) => {
699
+ const [x, y] = item;
700
+ this.#data[this.#layer][y][x] = item[2];
701
+ // redraw canvas
702
+ });
703
+ break;
704
+ }
705
+ }
706
+
707
+ redo() {
708
+ // ToDo: Rewrite to use new history api
709
+ /*const revert = this.#redoHistory.pop();
710
+ if (!revert) { return; }
711
+ this.#undoHistory.push(revert);
712
+ revert?.forEach((item) => {
713
+ const [x, y] = item;
714
+ this.#data[y][x] = item[2];
715
+ // redraw canvas
716
+ });*/
717
+ }
718
+
719
+ rotateClockwise() {
720
+ this.#rotate(false);
721
+ }
722
+
723
+ rotateCounterclockwise() {
724
+ this.#rotate(true);
725
+ }
726
+
727
+ #rotate(counterClockwise: boolean = false) {
728
+ const cloned = cloneGrid(this.#data[this.#layer]);
729
+ if (counterClockwise) {
730
+ const newData = this.#data[0].map((val, index) => this.#data.map(row => row[row.length - 1 - index]));
731
+ for (let iy = 0; iy < this.height; iy++) {
732
+ for (let ix = 0; ix < this.width; ix++) {
733
+ cloned[iy][ix] = newData[this.#layer][iy][ix];
734
+ }
735
+ }
736
+ } else {
737
+ const newData = this.#data[0].map((val, index) => this.#data.map(row => row[index]).reverse());
738
+ for (let iy = 0; iy < this.height; iy++) {
739
+ for (let ix = 0; ix < this.width; ix++) {
740
+ cloned[iy][ix] = newData[this.#layer][iy][ix];
741
+ }
742
+ }
743
+ }
744
+ this.#data[this.#layer] = cloned;
745
+ }
746
+
747
+ hasUndo() {
748
+ return this.#undoHistory.length !== 0;
749
+ }
750
+
751
+ hasRedo() {
752
+ return this.#redoHistory.length !== 0;
753
+ }
754
+
755
+ inputModePixel() {
756
+ this.#inputMode = InputMode.Pixel;
757
+ }
758
+
759
+ inputModeLine() {
760
+ this.#inputMode = InputMode.Line;
761
+ }
762
+
763
+ inputModeRectangle() {
764
+ this.#inputMode = InputMode.Rectangle;
765
+ }
766
+
767
+ inputModeRectangleOutline() {
768
+ this.#inputMode = InputMode.RectangleOutline;
769
+ }
770
+
771
+ inputModeEllipse() {
772
+ this.#inputMode = InputMode.Ellipse;
773
+ }
774
+
775
+ inputModeEllipseOutline() {
776
+ this.#inputMode = InputMode.EllipseOutline;
777
+ }
778
+
779
+ async save(options: FileOptions = {}): Promise<File> {
780
+ const file: File = {
781
+ width: this.width,
782
+ height: this.height,
783
+ transparent: this.transparent,
784
+ colors: this.#colors,
785
+ layers: this.#layers,
786
+ data: this.#data
787
+ };
788
+ if (options.history === true) {
789
+ file.undo = this.#undoHistory;
790
+ file.redo = this.#redoHistory;
791
+ }
792
+ // Trim data
793
+ for (let l = 0; l < file.data.length; l++) {
794
+ for (let y = file.data[l].length - 1; y >= 0; y--) {
795
+ if (y >= this.height) {
796
+ file.data[l].pop();
797
+ continue;
798
+ }
799
+ for (let x = file.data[l][y].length - 1; x >= 0; x--) {
800
+ if (x >= this.width) {
801
+ file.data[l][y].pop();
802
+ }
803
+ }
804
+ }
805
+ }
806
+ // Output
807
+ return file;
808
+ }
809
+
810
+ async open(json: File) {
811
+ if (typeof json !== 'object') {
812
+ return ['json must be type object'];
813
+ }
814
+ const errors: string[] = [];
815
+ // Validate 6 properties exist
816
+ const keys = Object.keys(json);
817
+ const required = ['width', 'height', 'transparent', 'colors', 'layers', 'data'];
818
+ required.forEach((key) => {
819
+ if (!keys.includes(key)) {
820
+ errors.push(`JSON key '${key}' required.`);
821
+ }
822
+ });
823
+ // Set props
824
+ this.width = json.width;
825
+ this.height = json.height;
826
+ this.transparent = json.transparent;
827
+ this.#colors = json.colors;
828
+ this.#layers = json.layers;
829
+ this.#data = json.data;
830
+ if (json.undo) {
831
+ this.#undoHistory = json.undo;
832
+ }
833
+ if (json.redo) {
834
+ this.#redoHistory = json.redo;
835
+ }
836
+ this.#init();
837
+ }
838
+
839
+ }