Open insinfo opened 9 months ago
I just saw that change detection works if I call _changeDetectorRef.detectChanges();
import 'dart:async';
import 'dart:html' as html;
import 'dart:js_util';
import 'dart:math';
import 'package:ngdart/angular.dart';
import 'package:rava_frontend/src/shared/directives/value_accessors/custom_form_directives.dart';
// ignore: unused_import
import 'package:rava_frontend/src/shared/js_interop/bootstrap_interop.dart';
import 'package:rava_frontend/src/shared/js_interop/fabric_interop.dart';
import 'package:rava_frontend/src/shared/utils/flatcolor.dart';
class CustomSize {
num width;
num height;
CustomSize({required this.width, required this.height});
}
@Component(
selector: 'cracha-editor-comp',
templateUrl: 'cracha_editor_page.html',
styleUrls: ['cracha_editor_page.css'],
directives: [
routerDirectives,
formDirectives,
//customFormDirectives,
],
)
class CrachaEditorPage
implements OnInit, AfterContentInit, AfterViewInit, OnDestroy {
@ViewChild('sidebar')
html.HtmlElement? sidebarElement;
// ignore: unused_field
final ChangeDetectorRef _changeDetectorRef;
@ViewChild('viewport')
html.DivElement? viewport;
@ViewChild('viewportInner')
html.DivElement? viewportInner;
@ViewChild('canvasEl')
html.CanvasElement? canvasEl;
final html.Element nativeElement;
late Canvas canvas;
Rect? stage;
/// tamanho da area de desenho (Prancheta)
// double currentWidth = 637;
// double currentHeight = 1012;
final CustomSize currentSize = CustomSize(width: 637, height: 1012);
CrachaEditorPage(this.nativeElement, this._changeDetectorRef);
StreamSubscription? ssOnResize;
@override
void ngOnInit() {
ssOnResize = html.window.onResize.listen(onResize);
}
@override
void ngAfterContentInit() {}
@override
void ngAfterViewInit() async {
//https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements
//canvasEl.width = nativeElement.
//html.window.getComputedStyleMap();
await Future.delayed(Duration(milliseconds: 20));
// final comStyle = viewportInner!.getComputedStyle();
// print('height: ${comStyle.height}');
// print('width: ${comStyle.width}');
// final rect = viewportInner!.getBoundingClientRect();
// print('height: ${rect.height}');
// print('width: ${rect.width}');
// var width = viewportInner!.offsetWidth;
// var height = viewportInner!.offsetHeight;
setElementCanvasSize();
initFabric();
}
void onResize(e) {
// setCanvasSize();
}
void updateSize() {
final activeObj = canvas.getActiveObject();
// print('updateSize ${activeObj == null}');
// consoleLog(activeObj);
// print(activeObj);
if (activeObj == null) {
stage?.set('width', currentSize.width);
stage?.set('height', currentSize.height);
} else {
activeObj.set('width', currentSize.width);
activeObj.set('height', currentSize.height);
}
canvas.renderAll();
}
void setElementCanvasSize() {
final ele = viewportInner!;
// print('clientHeight: ${ele.clientHeight}');
// print('clientWidth: ${ele.clientWidth}');
canvasEl!.height = ele.clientHeight;
canvasEl!.width = ele.clientWidth;
}
void initFabric() {
canvas = Canvas(canvasEl);
stage = Rect(
jsify({
'left': (canvas.width / 2) -
((currentSize.width * (stage?.scaleX ?? 1)) / 2),
'top': 50,
'width': currentSize.width,
'height': currentSize.height,
'fill': '#fff',
'lockMovementY': true,
'lockMovementX': true,
'selectable': false,
'hoverCursor': 'default',
}),
);
canvas.add(stage);
num zoom = 1;
canvas.on('mouse:wheel', allowInterop((opt) {
final deltaY = opt.e.deltaY;
//var deltaX = opt.e.deltaX;
final mousePoint = canvas.getPointer(opt.e, true);
zoom = canvas.getZoom();
zoom *= pow(0.999, deltaY);
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
//min(max(zoom + (deltaY/150), .5), 6)
//canvas.setZoom(zoom);
canvas.zoomToPoint(Point(mousePoint.x, mousePoint.y), zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
}));
canvas.on('selection:created', allowInterop((obj) {
onSelectObject(obj);
}));
canvas.on('selection:updated', allowInterop((obj) {
onSelectObject(obj);
}));
canvas.on('selection:cleared', allowInterop((event) {
//change detection doesn't work
onUnSelectObject(event);
}));
}
void onUnSelectObject(event) {
//change current width of stage (valid drawing area)
currentSize.width = stage!.getScaledWidth().toDouble();
currentSize.height = stage!.getScaledHeight().toDouble();
_changeDetectorRef.detectChanges();
}
void onSelectObject(event) {
final activeObj = canvas.getActiveObject();
final scaledWidth = activeObj.getScaledWidth();
final scaledHeight = activeObj.getScaledHeight();
currentSize.width = scaledWidth;
currentSize.height = scaledHeight;
_changeDetectorRef.detectChanges();
}
void addObject(String name) {
switch (name) {
case 'rect':
var rect = Rect(
jsify({
'left': (canvas.width / 2) - ((100 * (1)) / 2),
'top': 50,
'width': 100,
'height': 100,
'fill': FlatColor().generateHex2(),
}),
);
canvas.add(rect);
break;
default:
}
}
//https://www.riodasostras.rj.gov.br/cdn/Vendor/limitless/4.0/bs5/template/html/layout_1/full/assets/js/app.js
// Toggle component sidebar
void sidebarComponentToggle(e) {
e.preventDefault();
sidebarElement?.classes.toggle('sidebar-mobile-expanded');
}
@override
void ngOnDestroy() {
ssOnResize?.cancel();
}
}
fabric_interop.dart
@JS()
library fabric;
import 'dart:html';
import 'package:js/js.dart';
/// canvas = new fabric.Canvas(this.htmlCanvas.nativeElement, {
/// hoverCursor: 'pointer',
/// selection: true,
/// selectionBorderColor: 'blue',
/// isDrawingMode: true
/// });
@JS('fabric.Canvas')
class Canvas {
external Canvas(Element? element, [config]);
external add(dynamic element);
external renderAll();
external on(String event, dynamic func);
external Point getPointer(Event e, [bool ignoreZoom]);
external num getZoom();
external Canvas zoomToPoint(Point point, num value);
external Canvas setZoom(num value);
external dynamic /*Object|Null*/ getActiveObject();
external get width;
external get height;
}
@anonymous
@JS()
abstract class IObjectOptions {
external num get width;
external set width(num v);
external num get height;
external set height(num v);
external num get scaleX;
/// rect.set('fill', 'red');
/// rect.set({ strokeWidth: 5, stroke: 'rgba(100,200,200,0.5)' });
/// rect.set('angle', 15).set('flipY', true);
external set(dynamic propNameOrMap, dynamic val);
external scaleToWidth(dynamic val);
external scaleToHeight(dynamic val);
external num getScaledWidth();
external num getScaledHeight();
}
@JS('fabric.Rect')
class Rect extends IObjectOptions {
external Rect(options);
}
@JS('fabric.Circle')
class Circle extends IObjectOptions {
external Circle(options);
}
@JS('fabric.Triangle')
class Triangle extends IObjectOptions {
external Triangle(options);
}
@JS("fabric.Point")
class Point {
external num x;
external num y;
external factory Point(num x, num y);
}
The temporary solution is to use detectChanges() or create a variable member of the component to obtain the custom angular zone and use this zone (Zone.run) when changing a property linked to the template within a function called by javascript
// ignore_for_file: deprecated_member_use
import 'dart:async';
import 'dart:html' as html;
import 'dart:js_util';
import 'dart:math';
import 'package:ngdart/angular.dart';
import 'package:rava_frontend/src/shared/directives/value_accessors/custom_form_directives.dart';
// ignore: unused_import
import 'package:rava_frontend/src/shared/js_interop/bootstrap_interop.dart';
import 'package:rava_frontend/src/shared/js_interop/fabric_interop.dart';
import 'package:rava_frontend/src/shared/utils/flatcolor.dart';
class CustomSize {
num width;
num height;
CustomSize({required this.width, required this.height});
}
@Component(
selector: 'cracha-editor-comp',
templateUrl: 'cracha_editor_page.html',
styleUrls: ['cracha_editor_page.css'],
directives: [
routerDirectives,
formDirectives,
//customFormDirectives,
],
)
class CrachaEditorPage
implements OnInit, AfterContentInit, AfterViewInit, OnDestroy {
@ViewChild('sidebar')
html.HtmlElement? sidebarElement;
// ignore: unused_field
final ChangeDetectorRef _changeDetectorRef;
@ViewChild('viewport')
html.DivElement? viewport;
@ViewChild('viewportInner')
html.DivElement? viewportInner;
@ViewChild('canvasEl')
html.CanvasElement? canvasEl;
final html.Element nativeElement;
late Canvas canvas;
Rect? stage;
Zone angularZone = Zone.current;
/// tamanho da area de desenho (Prancheta)
// double currentWidth = 637;
// double currentHeight = 1012;
final CustomSize currentSize = CustomSize(width: 637, height: 1012);
CrachaEditorPage(this.nativeElement, this._changeDetectorRef);
StreamSubscription? ssOnResize;
@override
void ngOnInit() {
ssOnResize = html.window.onResize.listen(onResize);
}
@override
void ngAfterContentInit() {}
@override
void ngAfterViewInit() async {
//https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements
//canvasEl.width = nativeElement.
//html.window.getComputedStyleMap();
await Future.delayed(Duration(milliseconds: 20));
// final comStyle = viewportInner!.getComputedStyle();
// print('height: ${comStyle.height}');
// print('width: ${comStyle.width}');
// final rect = viewportInner!.getBoundingClientRect();
// print('height: ${rect.height}');
// print('width: ${rect.width}');
// var width = viewportInner!.offsetWidth;
// var height = viewportInner!.offsetHeight;
setElementCanvasSize();
initFabric();
}
void onResize(e) {
// setCanvasSize();
}
void updateSize() {
final activeObj = canvas.getActiveObject();
// print('updateSize ${activeObj == null}');
// consoleLog(activeObj);
// print(activeObj);
if (activeObj == null) {
stage?.set('width', currentSize.width);
stage?.set('height', currentSize.height);
} else {
activeObj.scaleToWidth(currentSize.width);
activeObj.scaleToHeight(currentSize.height);
// img.scaleToWidth(canvas.width / 2);
}
canvas.renderAll();
}
void setElementCanvasSize() {
final ele = viewportInner!;
// print('clientHeight: ${ele.clientHeight}');
// print('clientWidth: ${ele.clientWidth}');
canvasEl!.height = ele.clientHeight;
canvasEl!.width = ele.clientWidth;
}
void initFabric() {
canvas = Canvas(canvasEl);
stage = Rect(
jsify({
'left': (canvas.width / 2) -
((currentSize.width * (stage?.scaleX ?? 1)) / 2),
'top': 50,
'width': currentSize.width,
'height': currentSize.height,
'fill': '#fff',
'lockMovementY': true,
'lockMovementX': true,
'selectable': false,
'hoverCursor': 'default',
}),
);
canvas.add(stage);
num zoom = 1;
canvas.on('mouse:wheel', allowInterop((opt) {
final deltaY = opt.e.deltaY;
//var deltaX = opt.e.deltaX;
final mousePoint = canvas.getPointer(opt.e, true);
zoom = canvas.getZoom();
zoom *= pow(0.999, deltaY);
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
//min(max(zoom + (deltaY/150), .5), 6)
//canvas.setZoom(zoom);
canvas.zoomToPoint(Point(mousePoint.x, mousePoint.y), zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
}));
canvas.on('selection:created', allowInterop((obj) {
onSelectObject(obj);
}));
canvas.on('selection:updated', allowInterop((obj) {
onSelectObject(obj);
}));
canvas.on('selection:cleared', allowInterop((event) {
onUnSelectObject(event);
}));
canvas.on('object:modified', allowInterop((e) => onUnSelectObject(e)));
}
void onUnSelectObject(event) {
//change current width of stage (valid drawing area)
currentSize.width = stage!.width;
currentSize.height = stage!.height;
_changeDetectorRef.detectChanges();
}
void onModifiedObject(event) {
print('onModifiedObject $event');
consoleLog(event);
}
void onSelectObject(event) {
// print('onSelectObject Zone.current ${Zone.current} | angularZone: ${angularZone}');
final activeObj = canvas.getActiveObject();
final scaledWidth = activeObj.getScaledWidth();
final scaledHeight = activeObj.getScaledHeight();
//execute code on Zone of Angular
angularZone.run(() {
currentSize.width = scaledWidth;
currentSize.height = scaledHeight;
});
//_changeDetectorRef.detectChanges();
}
void addObject(String name) {
switch (name) {
case 'rect':
final item = Rect(
jsify({
'left': (canvas.width / 2) - ((100 * (1)) / 2),
'top': 50,
'width': 100,
'height': 100,
'fill': FlatColor().generateHex2(),
}),
);
canvas.add(item);
break;
case 'circle':
final item = Circle(
jsify({
'left': (canvas.width / 2) - ((100 * (1)) / 2),
'top': 100,
'radius': 50,
'fill': FlatColor().generateHex2(),
}),
);
canvas.add(item);
break;
default:
}
}
//https://www.riodasostras.rj.gov.br/cdn/Vendor/limitless/4.0/bs5/template/html/layout_1/full/assets/js/app.js
// Toggle component sidebar
void sidebarComponentToggle(e) {
e.preventDefault();
sidebarElement?.classes.toggle('sidebar-mobile-expanded');
}
@override
void ngOnDestroy() {
ssOnResize?.cancel();
}
}
@GZGavinZhao @ykmnkmi I think the best solution would be to have an option to disable the use of Zones in AngularDart
@insinfo Have you tried this?
@GZGavinZhao @ykmnkmi
Which ng* package(s) are the source of the bug?
ngdart
Which operating system(s) does this bug appear on?
Windows
Which browser(s) does this bug appear on?
Chrome 117.0.5938.132 64 bits
Is this a regression?
No
Description
I'm using allowInterop to interact with JavaScript code, more specifically I'm creating an AngularDart application that uses Fabric.js to create a Badge Creator (Professional Employee ID Card), so I'm having a problem because it seems that AngularDart doesn't detect changes in variables What do I change inside allowInterop
Please provide the steps to reproduce the bug
Please provide the exception or error you saw
Please provide the dependency environment you discovered this bug in (run
dart pub deps -s compact
)Anything else?
No response