firefly-compiler 0.6.10 → 0.6.12

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/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,79 @@ 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
+ hook.setValue(newValue)
287
300
  self.renderQueue.push(RenderQueueItem(
288
- luxCopy = luxCopy
301
+ luxCopy = hook.luxCopy
289
302
  render = {
290
- let child = luxCopy.element.child
291
- let element = luxCopy.element.childAt(child)
292
303
  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
304
  }
299
305
  ))
300
306
  }
301
- try {
302
- body(value, {setState(_)})
303
- } finally {
304
- self.depth -= 1
305
- }
307
+ body(value, {setState(_)})
306
308
  }
307
309
 
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)
310
+ useCallback1[A1](callback: A1 => Unit, body: (A1 => Unit) => Unit) {
311
+ self.useHook: hook =>
312
+ if(hook.dry()) {body(callback)} else:
313
+ hook.setValue(callback)
314
+ body {a1 =>
315
+ let latestCallback = hook.grabValue()
316
+ self.renderLock.do {
317
317
  latestCallback(a1)
318
+ processRenderQueue(self)
318
319
  }
319
- } finally {
320
- self.depth -= 1
321
320
  }
322
321
  }
323
322
 
324
323
  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
324
+ self.useHook: hook =>
325
+ if(hook.dry()) {body(a1)} else:
326
+ if(!hook.hasValue() || hook.grabValue() != a1) {
327
+ hook.setValue(a1)
328
+ body(a1)
329
+ } else {
330
+ self.element.keepChildren = True
338
331
  }
339
332
  }
340
333
 
341
334
  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
335
+ self.useHook: hook =>
336
+ if(hook.dry()) {body(compute(a1))} else:
337
+ if(hook.hasValue()) {
338
+ let pair: Pair[A1, T] = hook.grabValue()
339
+ if(pair.first == a1) {
340
+ body(pair.second)
341
+ } else {
342
+ let value = compute(a1)
343
+ hook.setValue(Pair(a1, value))
344
+ body(value)
357
345
  }
358
- body(computed)
359
- } finally {
360
- self.depth -= 1
346
+ } else {
347
+ let value = compute(a1)
348
+ hook.setValue(Pair(a1, value))
349
+ body(value)
361
350
  }
362
351
  }
363
-
352
+
364
353
  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(
354
+ self.useHook: hook =>
355
+ if(hook.dry()) {suspense()} else:
356
+ hook.cleanup()
357
+ let fragment = hook.luxCopy.document.createFragment()
358
+ let subtask = hook.luxCopy.task.spawn {task =>
359
+ let lux = hook.luxCopy.Lux(
374
360
  task = task
375
361
  renderLock = task.lock()
376
362
  renderQueue = Array.new()
377
- element = luxCopy.element.LuxElement(element = fragment)
363
+ element = hook.luxCopy.element.LuxElement(element = fragment)
378
364
  )
379
365
  try {
380
366
  body(lux)
381
367
  task.throwIfAborted()
382
- lux.copyFrom(luxCopy)
368
+ lux.copyFrom(hook.luxCopy)
383
369
  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())
370
+ replaceWithFragment(lux.element.element, fragment, lux.depth)
387
371
  }
388
372
  } catchAny {error =>
389
373
  if(error.name() != "AbortError") {
@@ -391,10 +375,128 @@ extend self: Lux {
391
375
  }
392
376
  }
393
377
  }
378
+ hook.setCleanup {subtask.abort()}
394
379
  suspense()
395
- setTaskOnElement(self.element.childAt(self.element.child - 1), Some(subtask))
396
380
  }
397
381
 
382
+ useWebSocket(
383
+ url: String
384
+ onMessage: Buffer => Unit
385
+ body: (Buffer => Unit) => Unit
386
+ ) {
387
+ self.useHook: hook =>
388
+ if(hook.dry()) {} else:
389
+ self.useCallback1 {buffer =>
390
+ onMessage(buffer)
391
+ }: onMessage =>
392
+ function createWebSocket(): JsValue {
393
+ let socket = Js->WebSocket->(url)
394
+ hook.setValue(Pair(socket, url))
395
+ hook.setCleanup {
396
+ socket->close()
397
+ }
398
+ socket->binaryType = "arraybuffer"
399
+ Js.awaitCancellablePromise {resolve, reject, onSettle =>
400
+ socket->onopen = Js->{resolve(Unit)}
401
+ socket->onclose = Js->{reject(_?)}
402
+ }
403
+ socket->onopen = Js.null()
404
+ socket->onclose = Js.null()
405
+ socket->addEventListener("close", Js->{
406
+ Log.debug("Closed")
407
+ })
408
+ socket->addEventListener("message", Js->{m =>
409
+ let buffer = Js->DataView->(m->data).grabBuffer() // TODO: Handle text
410
+ onMessage(buffer)
411
+ })
412
+ socket->addEventListener("error", Js->{e =>
413
+ Log.debug("Error")
414
+ })
415
+ socket
416
+ }
417
+ let socket = if(!hook.hasValue()) {
418
+ createWebSocket()
419
+ } else {
420
+ let pair = hook.grabValue()
421
+ let oldSocket: JsValue = pair.first
422
+ let oldUrl: String = pair.second
423
+ if(oldUrl == url) {
424
+ oldSocket
425
+ } else {
426
+ hook.cleanup()
427
+ createWebSocket()
428
+ }
429
+ }
430
+ let send: Buffer => Unit = {buffer => socket->send(buffer)}
431
+ body(send)
432
+ }
433
+
434
+ }
435
+
436
+ capability LuxHook[T](luxCopy: Lux)
437
+
438
+ extend self[T]: LuxHook[T] {
439
+ dry(): Bool {
440
+ !self.luxCopy.dry.isEmpty()
441
+ }
442
+ setValue(value: T) {
443
+ self.luxCopy.element.element.set("_lux$S" + self.luxCopy.depth, value!)
444
+ }
445
+ hasValue(): Bool {
446
+ self.luxCopy.element.element.hasOwn("_lux$S" + self.luxCopy.depth)
447
+ }
448
+ grabValue(): T {
449
+ let value = self.luxCopy.element.element.get("_lux$S" + self.luxCopy.depth)
450
+ if(value.isUndefined() && !self.hasValue()) {
451
+ throw(GrabException)
452
+ }
453
+ value? // If value is an async function, will the effect type system see through this?
454
+ }
455
+ setCleanup(body: () => Unit) {
456
+ self.luxCopy.element.element.set("_lux$C" + self.luxCopy.depth, body!)
457
+ }
458
+ cleanup() {
459
+ let key = "_lux$C" + self.luxCopy.depth
460
+ let cleanupFunction = self.luxCopy.element.element.get(key)
461
+ self.luxCopy.element.element.set(key, {}!)
462
+ cleanupFunction?() // Async cleanup is not waited for. Is that ok?
463
+ }
464
+ }
465
+
466
+ cleanupElement(element: JsValue, depth: Int) {
467
+ mutable d = depth
468
+ doWhile {
469
+ let cleanupFunction = element.get("_lux$C" + d)
470
+ if(cleanupFunction.isUndefined()) {
471
+ False
472
+ } else {
473
+ Js->Reflect->deleteProperty(element, "_lux$C" + d)
474
+ Js->Reflect->deleteProperty(element, "_lux$S" + d)
475
+ cleanupFunction?() // Async cleanup is not waited for. Is that ok?
476
+ d += 1
477
+ True
478
+ }
479
+ }
480
+ element->children.each {child =>
481
+ cleanupElement(child, d + 1)
482
+ }
483
+ }
484
+
485
+ replaceWithFragment(element: JsValue, fragment: JsValue, depth: Int) {
486
+ cleanupElement(element, depth) // Assumes that hooks have no siblings
487
+ mutable d = depth
488
+ doWhile {
489
+ let cleanupFunction = fragment.get("_lux$C" + d)
490
+ if(cleanupFunction.isUndefined()) {
491
+ False
492
+ } else {
493
+ element.set("_lux$C" + d, cleanupFunction)
494
+ if(fragment.hasOwn("_lux$S" + d)) {element.set("_lux$S" + d, fragment.get("_lux$S" + d))}
495
+ d += 1
496
+ True
497
+ }
498
+ }
499
+ element->replaceChildren(fragment)
398
500
  }
399
501
 
400
502
  processRenderQueue(self: Lux) {
@@ -411,50 +513,14 @@ processRenderQueue(self: Lux) {
411
513
  }
412
514
  }
413
515
 
414
- removeCurrentChild(self: Lux): Bool {
516
+ removeCurrentChild(self: Lux, depth: Int): Bool {
415
517
  let child = self.element.childAt(self.element.child)
416
518
  if(!child.isNullOrUndefined() && !child->children.isNullOrUndefined()) {
417
- abortTasksOnElement(self, child, False)
519
+ cleanupElement(child, depth)
418
520
  }
419
521
  self.element.removeAt(self.element.child)
420
522
  }
421
523
 
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
524
  patchText(self: Lux) {
459
525
  if(!self.texts.isEmpty()):
460
526
  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.10",
7
+ "version": "0.6.12",
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.10",
7
+ "version": "0.6.12",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Ahnfelt/firefly-boot"
@@ -75,7 +75,7 @@ extend self[P1: WebParameter]: WebRoute1[P1] {
75
75
  extend self[P1: WebParameter, P2: WebParameter]: WebRoute2[P1, P2] {
76
76
  toUrl(p1: P1, p2: P2): String {
77
77
  mutable result = ""
78
- mutable parameter = 0
78
+ mutable parameter = 1
79
79
  self.segments.each {r =>
80
80
  if(r.startsWith("{")) {
81
81
  let p = if(parameter == 1) {