-
Notifications
You must be signed in to change notification settings - Fork 0
/
Maid.ts
204 lines (167 loc) · 4.42 KB
/
Maid.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Packages
import { GetUniqueId } from './UniqueId.ts'
import {
Signal, IsConnection,
type Event, type Connection, type CallbackDefinition
} from './Signal.ts'
import { type Scheduled, IsScheduled, Cancel } from './Scheduler.ts'
// Local Types
type Callback = (() => void)
// Signal Types
type DestroyingSignal = (() => void)
type CleanedSignal = (() => void)
type DestroyedSignal = (() => void)
// Maid Types
type Item = (
Giveable
| Scheduled
| MutationObserver | ResizeObserver
| Element
| Signal<CallbackDefinition> | Connection
| Callback
)
export type GiveableItem = Item
abstract class Giveable {
abstract Destroy(): void
}
// Helper Methods
const IsGiveable = (item: object): item is Giveable => {
return ("Destroy" in item)
}
// Class
class Maid implements Giveable {
// Private Properties
private Items: Map<unknown, Item>
private DestroyedState: boolean
// Signals
private DestroyingSignal: Signal<DestroyingSignal>
private CleanedSignal: Signal<CleanedSignal>
private DestroyedSignal: Signal<DestroyedSignal>
// Events
public Destroying: Event<DestroyingSignal>
public Cleaned: Event<CleanedSignal>
public Destroyed: Event<DestroyedSignal>
// Constructor
constructor() {
// Create our list of items
this.Items = new Map()
// Store our initial destroyed state
this.DestroyedState = false
// Create our signals/events
{
// Signals
this.DestroyingSignal = new Signal()
this.CleanedSignal = new Signal()
this.DestroyedSignal = new Signal()
// Events
this.Destroying = this.DestroyingSignal.GetEvent()
this.Cleaned = this.CleanedSignal.GetEvent()
this.Destroyed = this.DestroyedSignal.GetEvent()
}
}
// Private Methods
private CleanItem<T extends Item>(item: T) {
// Check if we're a maid
if (IsGiveable(item)) {
item.Destroy()
} else if (IsScheduled(item)) {
Cancel(item)
} else if ((item instanceof MutationObserver) || (item instanceof ResizeObserver)) {
item.disconnect()
} else if (IsConnection(item)) {
item.Disconnect()
} else if(item instanceof Element) {
item.remove()
} else if (typeof item === "function") {
item()
} else {
console.warn("UNSUPPORTED MAID ITEM", typeof item, item)
}
}
// Public Methods
public Give<T extends Item>(item: T, key?: unknown): T {
// If we're already destroyed then we can just clean the item immediately
if (this.DestroyedState) {
this.CleanItem(item)
return item
}
// Determine our final-key
const finalKey = (key ?? GetUniqueId())
// Check if we already exist
if (this.Has(finalKey)) {
// Clean our previous item
this.Clean(finalKey)
}
// Now store ourselves
this.Items.set(finalKey, item)
// Return our item for ease-of-use
return item
}
public GiveItems<T extends Item[]>(...args: T): T {
// Loop through all of our items
for (const item of args) {
// Give the item
this.Give(item)
}
// Return back our items
return args
}
public Get<T extends Item>(key: unknown): (T | undefined) {
return (this.DestroyedState ? undefined : (this.Items.get(key) as T))
}
public Has(key: unknown): boolean {
return (this.DestroyedState ? false : this.Items.has(key))
}
public Clean(key: unknown) {
// If we are destroyed then we are already cleaned up everything
if (this.DestroyedState) {
return
}
// First determine if we have the item
const item = this.Items.get(key)
if (item !== undefined) {
// Remove the key
this.Items.delete(key)
// Clean the item
this.CleanItem(item)
}
}
public CleanUp() {
// If we're already destroyed then we're already cleaned-up
if (this.DestroyedState) {
return
}
// Loop through all of our items
for (const [key, _] of this.Items) {
// Clean the item
this.Clean(key)
}
// Make sure we aren't destroyed prior to firing
if (this.DestroyedState === false) {
this.CleanedSignal.Fire()
}
}
public IsDestroyed() {
return this.DestroyedState
}
// Deconstructor
public Destroy() {
// Make sure we don't perform twice
if (this.DestroyedState === false) {
// Fire our Destroying signal
this.DestroyingSignal.Fire()
// Clean out all our items
this.CleanUp()
// Set our destroyed state
this.DestroyedState = true
// Fire our destroyed signal
this.DestroyedSignal.Fire()
// Now destroy all our signals
this.DestroyingSignal.Destroy()
this.CleanedSignal.Destroy()
this.DestroyedSignal.Destroy()
}
}
}
// Export our maid class
export { Maid, Giveable }