firefly-compiler 0.6.11 → 0.6.13

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.
package/bin/Release.ff CHANGED
@@ -36,7 +36,7 @@ release(
36
36
  runSuccessful(system, "git", ["commit", "-a", "-m", "Autorelease_v" + version], system.path(".."))
37
37
  runSuccessful(system, "git", ["push"], system.path(".."))
38
38
  system.writeLine("Due to 2FA requirements, you must now manually run:")
39
- system.writeLine("cd ..; npm publish; npm install -g firefly-compiler")
39
+ system.writeLine("cd ..; npm login; npm publish; npm install -g firefly-compiler")
40
40
  //runSuccessful(system, "npm", ["install", "-g", "firefly-compiler"], system.path(".."))
41
41
  }
42
42
 
package/lux/Lux.ff CHANGED
@@ -169,7 +169,7 @@ extend self: Lux {
169
169
  } finally {
170
170
  patchText(self)
171
171
  if(!self.element.keepChildren) {
172
- doWhile {removeCurrentChild(self)}
172
+ doWhile {removeCurrentChild(self, self.depth)}
173
173
  }
174
174
  patchAttributes(self)
175
175
  self.depth -= 1
@@ -268,10 +268,24 @@ extend self: Lux {
268
268
  onClick(handler: LuxEvent => Unit) {self.on("click", handler)}
269
269
  onInput(handler: LuxEvent => Unit) {self.on("input", handler)}
270
270
 
271
- useState[T: HasAnyTag](initialValue: T, body: (T, T => Unit) => Unit) {
272
- self.dry.map {_ => body(initialValue, {_ => })}.else:
271
+ useHook[T](body: LuxHook[T] => Unit) {
272
+ // if(!self.canHook) {throw(LuxHookException)}
273
+ // self.canHook = False
273
274
  self.depth += 1
274
- let value = getStateOnElement(self.element.element, self.depth, initialValue)
275
+ try {
276
+ if(self.dry.isEmpty()) {
277
+ self.element.element.set("_lux$C" + self.depth, {}!)
278
+ }
279
+ body(LuxHook(self.Lux(element = self.element.LuxElement())))
280
+ } finally {
281
+ self.depth -= 1
282
+ }
283
+ }
284
+
285
+ useState[T: HasAnyTag](initialValue: T, body: (T, T => Unit) => Unit) {
286
+ self.useHook: hook =>
287
+ if(hook.dry()) {body(initialValue, {_ => })} else:
288
+ let value = if(hook.hasValue()) {hook.grabValue()} else {initialValue}
275
289
  mutable i = 0
276
290
  while {i < self.renderQueue.size()} {
277
291
  let item = self.renderQueue.grab(i)
@@ -281,109 +295,85 @@ extend self: Lux {
281
295
  i += 1
282
296
  }
283
297
  }
284
- let luxCopy = self.Lux(element = self.element.LuxElement())
285
298
  function setState(newValue: T): Unit {
286
- setStateOnElement(luxCopy.element.element, luxCopy.depth, newValue)
299
+ if(hook.luxCopy.element.element.hasOwn("_lux$R")) {
300
+ hook.luxCopy.element = hook.luxCopy.element.LuxElement(
301
+ element = hook.luxCopy.element.element->"_lux$R"
302
+ )
303
+ }
304
+ hook.setValue(newValue)
287
305
  self.renderQueue.push(RenderQueueItem(
288
- luxCopy = luxCopy
306
+ luxCopy = hook.luxCopy
289
307
  render = {
290
- let child = luxCopy.element.child
291
- let element = luxCopy.element.childAt(child)
292
308
  body(newValue, {setState(_)})
293
- if(!element.equals(luxCopy.element.childAt(child))) {
294
- removeCurrentChild(luxCopy.Lux(
295
- element = luxCopy.element.LuxElement(child = luxCopy.element.child + 1)
296
- ))
297
- }
298
309
  }
299
310
  ))
300
311
  }
301
- try {
302
- body(value, {setState(_)})
303
- } finally {
304
- self.depth -= 1
305
- }
312
+ body(value, {setState(_)})
306
313
  }
307
314
 
308
- useCallback1[A1: Equal: HasAnyTag](callback: A1 => Unit, body: (A1 => Unit) => Unit) {
309
- self.dry.map {_ => body(callback)}.else:
310
- self.depth += 1
311
- setStateOnElement(self.element.element, self.depth, callback)
312
- let element = self.element.element
313
- let depth = self.depth
314
- try {
315
- body {a1 =>
316
- let latestCallback = getStateOnElement(element, depth, callback)
315
+ useCallback1[A1](callback: A1 => Unit, body: (A1 => Unit) => Unit) {
316
+ self.useHook: hook =>
317
+ if(hook.dry()) {body(callback)} else:
318
+ hook.setValue(callback)
319
+ body {a1 =>
320
+ let latestCallback = hook.grabValue()
321
+ self.renderLock.do {
317
322
  latestCallback(a1)
323
+ processRenderQueue(self)
318
324
  }
319
- } finally {
320
- self.depth -= 1
321
325
  }
322
326
  }
323
327
 
324
328
  useLazy1[A1: Equal: HasAnyTag](a1: A1, body: A1 => Unit = {_ => }) {
325
- self.dry.map {_ => body(a1)}.else:
326
- self.depth += 1
327
- try {
328
- let old = getStateOnElement(self.element.element, self.depth, None)
329
- old.{
330
- | Some(o1) {a1 == o1} =>
331
- self.element.keepChildren = True
332
- | _ =>
333
- setStateOnElement(self.element.element, self.depth, Some(a1))
334
- body(a1)
335
- }
336
- } finally {
337
- self.depth -= 1
329
+ self.useHook: hook =>
330
+ if(hook.dry()) {body(a1)} else:
331
+ if(!hook.hasValue() || hook.grabValue() != a1) {
332
+ hook.setValue(a1)
333
+ body(a1)
334
+ } else {
335
+ self.element.keepChildren = True
338
336
  }
339
337
  }
340
338
 
341
339
  useMemo1[A1: Equal: HasAnyTag, T: HasAnyTag](a1: A1, compute: A1 => T, body: T => Unit) {
342
- self.dry.map {_ => body(compute(a1))}.else:
343
- self.depth += 1
344
- try {
345
- let old = getStateOnElement(self.element.element, self.depth, Pair(a1, None))
346
- let computed = old.{
347
- | Pair(o1, _) {a1 != o1} =>
348
- let v = compute(a1)
349
- setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
350
- v
351
- | Pair(_, Some(v)) =>
352
- v
353
- | Pair(_, None) =>
354
- let v = compute(a1)
355
- setStateOnElement(self.element.element, self.depth, Pair(a1, Some(v)))
356
- v
340
+ self.useHook: hook =>
341
+ if(hook.dry()) {body(compute(a1))} else:
342
+ if(hook.hasValue()) {
343
+ let pair: Pair[A1, T] = hook.grabValue()
344
+ if(pair.first == a1) {
345
+ body(pair.second)
346
+ } else {
347
+ let value = compute(a1)
348
+ hook.setValue(Pair(a1, value))
349
+ body(value)
357
350
  }
358
- body(computed)
359
- } finally {
360
- self.depth -= 1
351
+ } else {
352
+ let value = compute(a1)
353
+ hook.setValue(Pair(a1, value))
354
+ body(value)
361
355
  }
362
356
  }
363
-
357
+
364
358
  useSuspense(suspense: () => Unit, body: Lux => Unit) {
365
- self.dry.map {_ => suspense()}.else:
366
- let oldSubtask = getTaskOnElement(self.element.element)
367
- oldSubtask.each {task =>
368
- forceAsyncAbort(self, task)
369
- }
370
- let fragment = self.document.createFragment()
371
- let luxCopy = self.Lux(element = self.element.LuxElement())
372
- let subtask = self.task.spawn {task =>
373
- let lux = luxCopy.Lux(
359
+ self.useHook: hook =>
360
+ if(hook.dry()) {suspense()} else:
361
+ hook.cleanup()
362
+ suspense()
363
+ let fragment = hook.luxCopy.document.createFragment()
364
+ let subtask = hook.luxCopy.task.spawn {task =>
365
+ let lux = hook.luxCopy.Lux(
374
366
  task = task
375
367
  renderLock = task.lock()
376
368
  renderQueue = Array.new()
377
- element = luxCopy.element.LuxElement(element = fragment)
369
+ element = hook.luxCopy.element.LuxElement(element = fragment)
378
370
  )
379
371
  try {
380
372
  body(lux)
381
373
  task.throwIfAborted()
382
- lux.copyFrom(luxCopy)
374
+ lux.copyFrom(hook.luxCopy)
383
375
  lux.renderLock.do {
384
- abortTasksOnElement(lux, lux.element.childAt(lux.element.child), onlyChildren = True)
385
- lux.element.removeAt(lux.element.child)
386
- lux.element.insertBefore(fragment, lux.jsSystem.null())
376
+ replaceWithFragment(lux.element.element, fragment, lux.depth)
387
377
  }
388
378
  } catchAny {error =>
389
379
  if(error.name() != "AbortError") {
@@ -391,10 +381,128 @@ extend self: Lux {
391
381
  }
392
382
  }
393
383
  }
394
- suspense()
395
- setTaskOnElement(self.element.childAt(self.element.child - 1), Some(subtask))
384
+ hook.setCleanup {subtask.abort()}
385
+ }
386
+
387
+ useWebSocket(
388
+ url: String
389
+ onMessage: Buffer => Unit
390
+ body: (Buffer => Unit) => Unit
391
+ ) {
392
+ self.useHook: hook =>
393
+ if(hook.dry()) {} else:
394
+ self.useCallback1 {buffer =>
395
+ onMessage(buffer)
396
+ }: onMessage =>
397
+ function createWebSocket(): JsValue {
398
+ let socket = Js->WebSocket->(url)
399
+ hook.setValue(Pair(socket, url))
400
+ hook.setCleanup {
401
+ socket->close()
402
+ }
403
+ socket->binaryType = "arraybuffer"
404
+ Js.awaitCancellablePromise {resolve, reject, onSettle =>
405
+ socket->onopen = Js->{resolve(Unit)}
406
+ socket->onclose = Js->{reject(_?)}
407
+ }
408
+ socket->onopen = Js.null()
409
+ socket->onclose = Js.null()
410
+ socket->addEventListener("close", Js->{
411
+ Log.debug("Closed")
412
+ })
413
+ socket->addEventListener("message", Js->{m =>
414
+ let buffer = Js->DataView->(m->data).grabBuffer() // TODO: Handle text
415
+ onMessage(buffer)
416
+ })
417
+ socket->addEventListener("error", Js->{e =>
418
+ Log.debug("Error")
419
+ })
420
+ socket
421
+ }
422
+ let socket = if(!hook.hasValue()) {
423
+ createWebSocket()
424
+ } else {
425
+ let pair = hook.grabValue()
426
+ let oldSocket: JsValue = pair.first
427
+ let oldUrl: String = pair.second
428
+ if(oldUrl == url) {
429
+ oldSocket
430
+ } else {
431
+ hook.cleanup()
432
+ createWebSocket()
433
+ }
434
+ }
435
+ let send: Buffer => Unit = {buffer => socket->send(buffer)}
436
+ body(send)
437
+ }
438
+
439
+ }
440
+
441
+ capability LuxHook[T](luxCopy: Lux)
442
+
443
+ extend self[T]: LuxHook[T] {
444
+ dry(): Bool {
445
+ !self.luxCopy.dry.isEmpty()
446
+ }
447
+ setValue(value: T) {
448
+ self.luxCopy.element.element.set("_lux$S" + self.luxCopy.depth, value!)
449
+ }
450
+ hasValue(): Bool {
451
+ self.luxCopy.element.element.hasOwn("_lux$S" + self.luxCopy.depth)
452
+ }
453
+ grabValue(): T {
454
+ let value = self.luxCopy.element.element.get("_lux$S" + self.luxCopy.depth)
455
+ if(value.isUndefined() && !self.hasValue()) {
456
+ throw(GrabException)
457
+ }
458
+ value? // If value is an async function, will the effect type system see through this?
396
459
  }
460
+ setCleanup(body: () => Unit) {
461
+ self.luxCopy.element.element.set("_lux$C" + self.luxCopy.depth, body!)
462
+ }
463
+ cleanup() {
464
+ let key = "_lux$C" + self.luxCopy.depth
465
+ let cleanupFunction = self.luxCopy.element.element.get(key)
466
+ self.luxCopy.element.element.set(key, {}!)
467
+ cleanupFunction?() // Async cleanup is not waited for. Is that ok?
468
+ }
469
+ }
470
+
471
+ cleanupElement(element: JsValue, depth: Int) {
472
+ mutable d = depth
473
+ doWhile {
474
+ let cleanupFunction = element.get("_lux$C" + d)
475
+ if(cleanupFunction.isUndefined()) {
476
+ False
477
+ } else {
478
+ Js->Reflect->deleteProperty(element, "_lux$C" + d)
479
+ Js->Reflect->deleteProperty(element, "_lux$S" + d)
480
+ cleanupFunction?() // Async cleanup is not waited for. Is that ok?
481
+ d += 1
482
+ True
483
+ }
484
+ }
485
+ element->children.each {child =>
486
+ cleanupElement(child, d + 1)
487
+ }
488
+ }
397
489
 
490
+ replaceWithFragment(element: JsValue, fragment: JsValue, depth: Int) {
491
+ cleanupElement(element, depth) // Assumes that hooks have no siblings
492
+ mutable d = depth
493
+ doWhile {
494
+ let cleanupFunction = fragment.get("_lux$C" + d)
495
+ if(cleanupFunction.isUndefined()) {
496
+ False
497
+ } else {
498
+ element.set("_lux$C" + d, cleanupFunction)
499
+ if(fragment.hasOwn("_lux$S" + d)) {element.set("_lux$S" + d, fragment.get("_lux$S" + d))}
500
+ d += 1
501
+ True
502
+ }
503
+ }
504
+ fragment->"_lux$R" = element
505
+ element->replaceChildren(fragment)
398
506
  }
399
507
 
400
508
  processRenderQueue(self: Lux) {
@@ -411,50 +519,14 @@ processRenderQueue(self: Lux) {
411
519
  }
412
520
  }
413
521
 
414
- removeCurrentChild(self: Lux): Bool {
522
+ removeCurrentChild(self: Lux, depth: Int): Bool {
415
523
  let child = self.element.childAt(self.element.child)
416
524
  if(!child.isNullOrUndefined() && !child->children.isNullOrUndefined()) {
417
- abortTasksOnElement(self, child, False)
525
+ cleanupElement(child, depth)
418
526
  }
419
527
  self.element.removeAt(self.element.child)
420
528
  }
421
529
 
422
- getStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, fallback: T): T {
423
- let value = element.get("lux" + depth)
424
- if(value.isNullOrUndefined()) {fallback} else {
425
- value?
426
- }
427
- }
428
-
429
- setStateOnElement[T /*: HasAnyTag*/](element: JsValue, depth: Int, value: T): Unit {
430
- element.set("lux" + depth, value!)
431
- }
432
-
433
-
434
- getTaskOnElement(element: JsValue): Option[Task] {
435
- let value = element->luxTask
436
- if(value.isNullOrUndefined()) {None} else {
437
- value?
438
- }
439
- }
440
-
441
- setTaskOnElement(element: JsValue, task: Option[Task]): Unit {
442
- element->luxTask = task!
443
- }
444
-
445
- abortTasksOnElement(lux: Lux, element: JsValue, onlyChildren: Bool): Unit {
446
- if(!onlyChildren) {
447
- getTaskOnElement(element).each {task => forceAsyncAbort(lux, task)}
448
- }
449
- element->children.each {child =>
450
- abortTasksOnElement(lux, child, False)
451
- }
452
- }
453
-
454
- forceAsyncAbort(self: Lux, task: Task): Unit {
455
- task.abort()
456
- }
457
-
458
530
  patchText(self: Lux) {
459
531
  if(!self.texts.isEmpty()):
460
532
  let value = self.texts.drain().join()
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "description": "Firefly compiler",
5
5
  "author": "Firefly team",
6
6
  "license": "MIT",
7
- "version": "0.6.11",
7
+ "version": "0.6.13",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Ahnfelt/firefly-boot"
@@ -4,7 +4,7 @@
4
4
  "description": "Firefly language support",
5
5
  "author": "Firefly team",
6
6
  "license": "MIT",
7
- "version": "0.6.11",
7
+ "version": "0.6.13",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Ahnfelt/firefly-boot"