Skip to content

Commit d395738

Browse files
authored
Merge pull request #267 from NicDoesCode/edge
Release 1.3 to dev
2 parents 36a4ae7 + e8b3b83 commit d395738

35 files changed

+2031
-494
lines changed

wwwroot/assets/css/styles.css

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
body.sms-app .sms-loading {
2-
background: radial-gradient(circle, var(--bs-dark-bg-subtle) 15%, rgba(0, 0, 0, 0) 50%);
2+
background: radial-gradient(circle, var(--bs-dark-bg-subtle) 15%, rgba(0, 0, 0, 0) 50%);
33
transition: opacity 0.25s;
4-
top: 0;
4+
top: 0;
55
bottom: 0;
66
left: 0;
77
right: 0;
@@ -319,41 +319,69 @@ button:disabled img {
319319
padding-bottom: 4px;
320320
}
321321

322-
[data-bs-theme=dark] .sms-accordion-list button.sms-accordion-item {
322+
/* Styles for drag and drop on the palette select list */
323+
324+
.sms-accordion-list [data-list-item] {
325+
transition: border-width 100ms linear;
326+
}
327+
328+
.sms-accordion-list [data-list-item].list-insert-top {
329+
border-top: 5px solid var(--bs-primary) !important;
330+
}
331+
332+
.sms-accordion-list [data-list-item].list-insert-bottom,
333+
.sms-accordion-list [data-list-item]:last-of-type.list-insert-bottom {
334+
border-bottom: 5px solid var(--bs-primary) !important;
335+
}
336+
337+
.sms-accordion-list [data-list-item] button {
338+
transition: background-color 100ms linear;
339+
}
340+
341+
.sms-accordion-list [data-list-item] button:hover.sms-accordion-item {
323342
background-color: var(--bs-dark-bg-subtle);
324343
}
325344

326-
.sms-accordion-list button.sms-accordion-item.active {
345+
[data-bs-theme=dark] .sms-accordion-list [data-list-item] button.sms-accordion-item {
346+
background-color: var(--bs-dark-bg-subtle);
347+
}
348+
349+
[data-bs-theme=dark] .sms-accordion-list [data-list-item] button:hover.sms-accordion-item {
350+
background-color: var(--bs-black);
351+
}
352+
353+
.sms-accordion-list [data-list-item] button.sms-accordion-item.active {
327354
color: var(--bs-dark-text);
328355
background-color: var(--bs-secondary-bg);
329356
border-bottom: none !important;
330357
text-shadow: 0 0 3px #FFFFFF;
331358
}
332359

333-
[data-bs-theme=dark] .sms-accordion-list button.sms-accordion-item.active {
360+
[data-bs-theme=dark] .sms-accordion-list [data-list-item] button.sms-accordion-item.active {
334361
color: var(--bs-light-text-emphasis);
335362
text-shadow: 0 0 3px #000000;
336363
}
337364

338-
.sms-accordion-list button.sms-accordion-item .icon-expanded {
365+
.sms-accordion-list [data-list-item] button.sms-accordion-item .icon-expanded {
339366
display: none;
340367
}
341368

342-
.sms-accordion-list button.sms-accordion-item.active .icon-expand {
369+
.sms-accordion-list [data-list-item] button.sms-accordion-item.active .icon-expand {
343370
display: none;
344371
}
345372

346-
.sms-accordion-list button.sms-accordion-item.active.hidden-when-active,
347-
.sms-accordion-list button.sms-accordion-item.active .hidden-when-active {
373+
.sms-accordion-list [data-list-item] button.sms-accordion-item.active.hidden-when-active,
374+
.sms-accordion-list [data-list-item] button.sms-accordion-item.active .hidden-when-active {
348375
display: none !important;
349376
}
350377

351-
.sms-accordion-list.sms-accordion-list-bottom button.sms-accordion-item {
352-
border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
378+
.sms-accordion-list [data-list-item]:last-of-type {
353379
border-bottom: none !important;
380+
border-bottom-left-radius: var(--bs-card-border-radius);
381+
border-bottom-right-radius: var(--bs-card-border-radius);
354382
}
355383

356-
.sms-accordion-list.sms-accordion-list-bottom button.sms-accordion-item:last-of-type {
384+
.sms-accordion-list [data-list-item]:last-of-type button {
357385
border-bottom-left-radius: var(--bs-card-border-radius);
358386
border-bottom-right-radius: var(--bs-card-border-radius);
359387
}

wwwroot/modules/components/canvasManager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1235,7 +1235,7 @@ export default class CanvasManager {
12351235
const originalSmoothingEnabled = context.imageSmoothingEnabled;
12361236
const originalSmoothingQuality = context.imageSmoothingQuality;
12371237

1238-
context.imageSmoothingEnabled = true;
1238+
context.imageSmoothingEnabled = false;
12391239
context.imageSmoothingQuality = 'high';
12401240

12411241
const pxSize = coords.pxSize;
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import EventDispatcher from "../../components/eventDispatcher.js";
2+
import { DropPosition } from "../../types.js";
3+
4+
5+
const EVENT_OnCommand = 'EVENT_OnCommand';
6+
7+
const commands = {
8+
reorder: 'reorder'
9+
}
10+
11+
12+
/**
13+
* Manages the list re-ordering functions.
14+
*/
15+
export default class StackedListReorderHelper {
16+
17+
18+
/**
19+
* Gets a list of commands that this object can invoke.
20+
*/
21+
static get Commands() {
22+
return commands;
23+
}
24+
25+
26+
/** @type {HTMLElement} */
27+
#element;
28+
/** @type {EventDispatcher} */
29+
#dispatcher;
30+
/** @type {boolean} */
31+
#listMouseDown = false;
32+
/** @type {string?} */
33+
#originItemId = null;
34+
/** @type {number?} */
35+
#targetItemId = null;
36+
/** @type {string?} */
37+
#targetItemPosition = null;
38+
/** @type {string[]} */
39+
#itemIdList;
40+
/** @type {string} */
41+
#selectedItemId;
42+
/** @type {string} */
43+
#itemIdAttributeName = 'data-item-id';
44+
45+
46+
/**
47+
* @param {HTMLElement} element - HTML element that contains the stacked list that can be re-ordered.
48+
* @param {string} [itemIdAttributeNameOverride] - Overrides the attribute name that has the item ID.
49+
*/
50+
constructor(element, itemIdAttributeNameOverride) {
51+
52+
this.#element = element;
53+
this.#dispatcher = new EventDispatcher();
54+
55+
if (typeof itemIdAttributeNameOverride === 'string' && itemIdAttributeNameOverride !== null) {
56+
this.#itemIdAttributeName = itemIdAttributeNameOverride;
57+
}
58+
59+
document.addEventListener('blur', () => this.#resetTrackingVariables());
60+
document.addEventListener('mouseup', () => this.#resetTrackingVariables());
61+
this.#wireUpListContainer(this.#element);
62+
}
63+
64+
65+
/**
66+
* Registers a handler for a command.
67+
* @param {StackedListReorderHelperCommandCallback} callback - Callback that will receive the command.
68+
*/
69+
addHandlerOnCommand(callback) {
70+
this.#dispatcher.on(EVENT_OnCommand, callback);
71+
}
72+
73+
74+
#resetTrackingVariables() {
75+
this.#listMouseDown = false;
76+
this.#originItemId = null;
77+
this.#targetItemId = null;
78+
this.#targetItemPosition = null;
79+
}
80+
81+
82+
/**
83+
* @param {HTMLElement} listContainer
84+
*/
85+
#wireUpListContainer(listContainer) {
86+
if (!listContainer?.addEventListener) return;
87+
88+
listContainer.addEventListener('mouseup', (ev) => this.#handleListMouseUp(ev, listContainer));
89+
listContainer.addEventListener('mouseleave', (ev) => this.#handleListMouseLeave(ev, listContainer));
90+
listContainer.addEventListener('mousemove', (ev) => this.#handleListMouseMove(ev, listContainer));
91+
}
92+
93+
wireUpItems() {
94+
this.#itemIdList = [];
95+
this.#element.querySelectorAll(`[data-list-item]`).forEach((/** @type {HTMLElement} */ itemContainer, index) => {
96+
97+
const itemId = itemContainer.getAttribute(this.#itemIdAttributeName);
98+
this.#itemIdList.push(itemId);
99+
100+
itemContainer.addEventListener('mousedown', (ev) => {
101+
const itemRect = itemContainer.getBoundingClientRect();
102+
const pixelsFromTop = ev.clientY - itemRect.top;
103+
if (itemRect.height < 50 || pixelsFromTop < 30) {
104+
this.#listMouseDown = true;
105+
this.#originItemId = itemId;
106+
}
107+
});
108+
109+
});
110+
}
111+
112+
113+
/**
114+
* @param {MouseEvent} ev
115+
* @param {HTMLElement} listContainer
116+
*/
117+
#handleListMouseUp(ev, listContainer) {
118+
if (this.#targetItemId === null || this.#originItemId === null) return;
119+
if (this.#targetItemId === this.#originItemId) return;
120+
121+
/** @type {StackedListReorderHelperCommandEventArgs} */
122+
const args = {
123+
command: commands.reorder,
124+
originItemId: this.#originItemId,
125+
targetItemId: this.#targetItemId,
126+
targetItemPosition: this.#targetItemPosition
127+
};
128+
this.#dispatcher.dispatch(EVENT_OnCommand, args);
129+
}
130+
131+
/**
132+
* @param {MouseEvent} ev
133+
* @param {HTMLElement} listContainer
134+
*/
135+
#handleListMouseLeave(ev, listContainer) {
136+
const rect = listContainer.getBoundingClientRect();
137+
if (ev.clientX <= rect.left || ev.clientX >= rect.right || ev.clientY <= rect.top || ev.clientY >= rect.bottom) {
138+
listContainer.querySelectorAll(`[data-list-item]`).forEach((container) => {
139+
container.classList.remove('list-insert-top', 'list-insert-bottom');
140+
});
141+
}
142+
}
143+
144+
/**
145+
* @param {MouseEvent} ev
146+
* @param {HTMLElement} listContainer
147+
*/
148+
#handleListMouseMove(ev, listContainer) {
149+
if (!this.#listMouseDown) return;
150+
151+
this.#targetItemId = null;
152+
this.#targetItemPosition = null;
153+
154+
// If the container was found, do our calculations to work out next item ID etc
155+
const itemContainer = this.#crawlParentsToFindItemContainer(ev.target);
156+
if (!itemContainer) return;
157+
158+
const itemId = itemContainer.getAttribute(this.#itemIdAttributeName);
159+
160+
const rect = itemContainer.getBoundingClientRect();
161+
const nearTop = (ev.clientY - rect.top) < 15;
162+
const nearBottom = (rect.bottom - ev.clientY) < 15;
163+
164+
if (!nearTop && !nearBottom) return;
165+
166+
this.#targetItemId = itemId;
167+
this.#targetItemPosition = nearTop ? DropPosition.before : nearBottom ? DropPosition.after : null;
168+
169+
this.#setDropIndicatorCSS(this.#targetItemId, this.#targetItemPosition);
170+
}
171+
172+
/**
173+
* @param {HTMLElement} startingElement
174+
*/
175+
#crawlParentsToFindItemContainer(startingElement) {
176+
let result = startingElement;
177+
while (result && result !== this.#element) {
178+
if (result.hasAttribute('data-list-item')) return result;
179+
result = result.parentElement;
180+
}
181+
return null;
182+
}
183+
184+
/**
185+
* @param {string} itemId
186+
* @param {string} itemPosition - Either 'before' or 'after'.
187+
*/
188+
#setDropIndicatorCSS(itemId, itemPosition) {
189+
190+
/** @type {HTMLElement} */
191+
let itemContainerWithTopHighlighted = null;
192+
/** @type {HTMLElement} */
193+
let itemContainerWithBottomHighlighted = null;
194+
195+
const itemIndex = this.#itemIdList.indexOf(itemId);
196+
197+
if (itemPosition === DropPosition.before) {
198+
const itemIdBefore = (itemIndex - 1) >= 0 ? this.#itemIdList[itemIndex - 1] : null;
199+
itemContainerWithBottomHighlighted = this.#getListItemContainer(itemIdBefore);
200+
itemContainerWithTopHighlighted = this.#getListItemContainer(itemId);
201+
} else if (itemPosition === DropPosition.after) {
202+
const itemIdAfter = (itemIndex + 1) < this.#itemIdList.length ? this.#itemIdList[itemIndex + 1] : null;
203+
itemContainerWithTopHighlighted = this.#getListItemContainer(itemIdAfter);
204+
itemContainerWithBottomHighlighted = this.#getListItemContainer(itemId);
205+
}
206+
207+
itemContainerWithTopHighlighted?.classList.add('list-insert-top');
208+
itemContainerWithBottomHighlighted?.classList.add('list-insert-bottom');
209+
210+
// Reset CSS on all other containers
211+
this.#element.querySelectorAll(`[data-list-item]`).forEach((itemContainerToReset) => {
212+
if (itemContainerToReset !== itemContainerWithTopHighlighted) {
213+
itemContainerToReset.classList.remove('list-insert-top');
214+
}
215+
if (itemContainerToReset !== itemContainerWithBottomHighlighted) {
216+
itemContainerToReset.classList.remove('list-insert-bottom');
217+
}
218+
});
219+
}
220+
221+
/**
222+
* @param {string?} itemId
223+
* @returns {HTMLElement}
224+
*/
225+
#getListItemContainer(itemId) {
226+
if (!itemId) return null;
227+
return this.#element.querySelector(`[data-list-item][${this.#itemIdAttributeName}=${CSS.escape(itemId)}]`);
228+
}
229+
230+
231+
}
232+
233+
234+
/**
235+
* Callback for when a command event is invoked.
236+
* @callback StackedListReorderHelperCommandCallback
237+
* @param {StackedListReorderHelperCommandEventArgs} args - Arguments.
238+
* @exports
239+
*/
240+
/**
241+
* Arguments for a command event.
242+
* @typedef {Object} StackedListReorderHelperCommandEventArgs
243+
* @property {string} command - The command being invoked.
244+
* @property {string?} originItemId - Unique ID of the item that is to be moved.
245+
* @property {string?} targetItemId - Unique ID of the item that that the origin item is to be moved beside.
246+
* @property {string?} targetItemPosition - Either 'before' or 'after', indicates whether the origin item is to be placed before or after the target item.
247+
* @exports
248+
*/
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import ProjectEntry from "../models/projectEntry.js";
2+
3+
export default class ProjectEntryFactory {
4+
5+
6+
/**
7+
* Creates a new instance of the project entry class.
8+
* @param {ProjectEntryInitialValues} values - Initial values for the project entry.
9+
*/
10+
static create(values) {
11+
return new ProjectEntry(values.id, values.title, values.systemType, values.dateLastModified);
12+
}
13+
14+
15+
}
16+
17+
/**
18+
* @typedef {Object} ProjectEntryInitialValues
19+
* @property {string?} id - Unique ID of the new project, if not supplied one will be created.
20+
* @property {string?} title - Title, if not supplied one will be created.
21+
* @property {string?} systemType - Type of system targetted, either 'smsgg', 'gb' or 'nes', default is 'smsgg'.
22+
* @property {Date|number} dateLastModified - Date of last modification for the project.
23+
* @exports
24+
*/

0 commit comments

Comments
 (0)