docs(guide/lifecycle-hooks): add Dart version of example
NOTE: The Dart version generates fewer lifecycle events than the TS version. See angular/angular#6498 for details. closes #733
This commit is contained in:
parent
6ee3756989
commit
c4ae1f1b1b
|
@ -0,0 +1,94 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'logger_service.dart';
|
||||
import 'child_component.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'after-content',
|
||||
template: '''
|
||||
<div class="after-content">
|
||||
<div>-- child content begins --</div>
|
||||
|
||||
<ng-content></ng-content>
|
||||
|
||||
<div>-- child content ends --</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const ['.after-content {background: LightCyan; padding: 8px;}'])
|
||||
class AfterContentComponent
|
||||
implements AfterContentChecked, AfterContentInit, AfterViewInit {
|
||||
LoggerService _logger;
|
||||
|
||||
// Query for a CONTENT child of type `ChildComponent`
|
||||
@ContentChild(ChildComponent) ChildComponent contentChild;
|
||||
|
||||
// Query for a VIEW child of type`ChildComponent`
|
||||
// No such VIEW child exists!
|
||||
// This component holds content but no view of that type.
|
||||
@ViewChild(ChildComponent) ChildComponent viewChild;
|
||||
|
||||
String _prevHero;
|
||||
|
||||
AfterContentComponent(this._logger) {
|
||||
_logger.log('AfterContent ctor: $message');
|
||||
}
|
||||
|
||||
///// Hooks
|
||||
ngAfterContentInit() {
|
||||
// contentChild is set after the content has been initialized
|
||||
_logger.log('AfterContentInit: $message');
|
||||
}
|
||||
|
||||
get hasViewChild => viewChild != null;
|
||||
|
||||
ngAfterViewInit() {
|
||||
_logger
|
||||
.log('AfterViewInit: There is ${hasViewChild ? 'a' : 'no'} view child');
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
// contentChild is updated after the content has been checked
|
||||
// Called frequently; only report when the hero changes
|
||||
if (!hasContentChild || _prevHero == contentChild.hero) return;
|
||||
_prevHero = contentChild.hero;
|
||||
_logger.log('AfterContentChecked: $message');
|
||||
}
|
||||
|
||||
bool get hasContentChild => contentChild != null;
|
||||
|
||||
String get message => hasContentChild
|
||||
? '"${contentChild.hero}" child content'
|
||||
: 'no child content';
|
||||
}
|
||||
|
||||
@Component(
|
||||
selector: 'after-content-parent',
|
||||
template: '''
|
||||
<div class="parent">
|
||||
<h2>AfterContent</h2>
|
||||
|
||||
<after-content>
|
||||
<input [(ngModel)]="hero">
|
||||
<button (click)="showChild = !showChild">Toggle child view</button>
|
||||
|
||||
<my-child *ngIf="showChild" [hero]="hero"></my-child>
|
||||
</after-content>
|
||||
|
||||
<h4>-- Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="#msg of hookLog">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.parent {background: powderblue; padding: 8px; margin:100px 8px;}'
|
||||
],
|
||||
directives: const [AfterContentComponent, ChildComponent],
|
||||
providers: const [LoggerService])
|
||||
class AfterContentParentComponent {
|
||||
List<String> hookLog;
|
||||
String hero = 'Magneta';
|
||||
bool showChild = true;
|
||||
|
||||
AfterContentParentComponent(LoggerService logger) {
|
||||
hookLog = logger.logs;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'child_component.dart';
|
||||
import 'logger_service.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'after-view-parent',
|
||||
template: '''
|
||||
<div class="parent">
|
||||
<h2>AfterView</h2>
|
||||
|
||||
<div>
|
||||
<input [(ngModel)]="hero">
|
||||
<button (click)="showChild = !showChild">Toggle child view</button>
|
||||
|
||||
<my-child *ngIf="showChild" [hero]="hero"></my-child>
|
||||
</div>
|
||||
|
||||
<h4>-- Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="#msg of hookLog">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.parent {background: burlywood; padding: 8px; margin:100px 8px;}'
|
||||
],
|
||||
directives: const [ChildComponent],
|
||||
providers: const [LoggerService])
|
||||
class AfterViewParentComponent
|
||||
implements AfterContentInit, AfterViewChecked, AfterViewInit {
|
||||
LoggerService _logger;
|
||||
List<String> hookLog;
|
||||
String hero = 'Magneta';
|
||||
bool showChild = true;
|
||||
|
||||
// Query for a CONTENT child of type `ChildComponent`
|
||||
// No such CONTENT child exists!
|
||||
// This component holds a view but no content of that type.
|
||||
@ContentChild(ChildComponent)
|
||||
ChildComponent contentChild;
|
||||
|
||||
// Query for a VIEW child of type `ChildComponent`
|
||||
@ViewChild(ChildComponent)
|
||||
ChildComponent viewChild;
|
||||
|
||||
String _prevHero;
|
||||
|
||||
AfterViewParentComponent(this._logger) {
|
||||
hookLog = _logger.logs;
|
||||
_logger.log('AfterView ctor: $message');
|
||||
}
|
||||
|
||||
bool get _hasContentChild => contentChild != null;
|
||||
bool get _hasViewChild => viewChild != null;
|
||||
|
||||
///// Hooks
|
||||
ngAfterContentInit() {
|
||||
_logger.log(
|
||||
'AfterContentInit: There is ${ _hasContentChild ? 'a' : 'no'} content child');
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
// viewChild is set after the view has been initialized
|
||||
_logger.log('AfterViewInit: $message');
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
// viewChild is updated after the view has been checked
|
||||
// Called frequently; only report when the hero changes
|
||||
if (!_hasViewChild || _prevHero == viewChild.hero) return;
|
||||
_prevHero = viewChild.hero;
|
||||
_logger.log('AfterViewChecked: $message');
|
||||
}
|
||||
|
||||
String get message =>
|
||||
_hasViewChild ? '"${viewChild.hero}" child view' : 'no child view';
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'peek_a_boo_parent_component.dart';
|
||||
import 'on_changes_component.dart';
|
||||
import 'after_view_component.dart';
|
||||
import 'after_content_parent.dart';
|
||||
import 'spy_component.dart';
|
||||
import 'counter_component.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'my-app',
|
||||
template: '''
|
||||
<peek-a-boo-parent></peek-a-boo-parent>
|
||||
<on-changes-parent></on-changes-parent>
|
||||
<after-view-parent></after-view-parent>
|
||||
<after-content-parent></after-content-parent>
|
||||
<spy-parent></spy-parent>
|
||||
<counter-parent></counter-parent>
|
||||
''',
|
||||
directives: const [
|
||||
PeekABooParentComponent,
|
||||
OnChangesParentComponent,
|
||||
AfterViewParentComponent,
|
||||
AfterContentParentComponent,
|
||||
SpyParentComponent,
|
||||
CounterParentComponent
|
||||
])
|
||||
class AppComponent {}
|
|
@ -0,0 +1,19 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'my-child',
|
||||
template: '''
|
||||
<div class="my-child">
|
||||
<div>-- child view begins --</div>
|
||||
<div class="child">{{hero}} is my hero.</div>
|
||||
<div>-- child view ends --</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.child {background: Yellow; padding: 8px; }',
|
||||
'.my-child {background: LightYellow; padding: 8px; margin-top: 8px}'
|
||||
])
|
||||
class ChildComponent {
|
||||
@Input() String hero;
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'spy_directive.dart';
|
||||
import 'logger_service.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'my-counter',
|
||||
template: '''
|
||||
<div class="counter">
|
||||
Counter = {{counter}}
|
||||
|
||||
<h5>-- Counter Change Log --</h5>
|
||||
<div *ngFor="#chg of changeLog" mySpy>{{chg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.counter {background: LightYellow; padding: 8px; margin-top: 8px}'
|
||||
],
|
||||
directives: const [Spy])
|
||||
class MyCounter implements OnChanges {
|
||||
@Input() num counter;
|
||||
List<String> changeLog = [];
|
||||
|
||||
ngOnChanges(Map<String, SimpleChange> changes) {
|
||||
// Empty the changeLog whenever counter goes to zero
|
||||
// hint: this is a way to respond programmatically to external value changes.
|
||||
if (this.counter == 0) {
|
||||
changeLog.clear();
|
||||
}
|
||||
|
||||
// A change to `counter` is the only change we care about
|
||||
SimpleChange prop = changes['counter'];
|
||||
var prev = prop.isFirstChange() ? "{}" : prop.previousValue;
|
||||
changeLog.add(
|
||||
'counter: currentValue = ${prop.currentValue}, previousValue = $prev');
|
||||
}
|
||||
}
|
||||
|
||||
@Component(
|
||||
selector: 'counter-parent',
|
||||
template: '''
|
||||
<div class="parent">
|
||||
<h2>Counter Spy</h2>
|
||||
|
||||
<button (click)="updateCounter()">Update counter</button>
|
||||
<button (click)="reset()">Reset Counter</button>
|
||||
|
||||
<my-counter [counter]="value"></my-counter>
|
||||
|
||||
<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="#msg of spyLog">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.parent {background: gold; padding: 10px; margin:100px 8px;}'
|
||||
],
|
||||
directives: const [MyCounter],
|
||||
providers: const [LoggerService])
|
||||
class CounterParentComponent {
|
||||
num value;
|
||||
List<String> spyLog = [];
|
||||
|
||||
LoggerService _logger;
|
||||
|
||||
CounterParentComponent(this._logger) {
|
||||
spyLog = _logger.logs;
|
||||
reset();
|
||||
}
|
||||
|
||||
updateCounter() => value += 1;
|
||||
|
||||
reset() {
|
||||
_logger.log('-- reset --');
|
||||
value = 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:angular2/angular2.dart';
|
||||
import 'dart:async';
|
||||
|
||||
@Injectable()
|
||||
class LoggerService {
|
||||
List<String> logs = [];
|
||||
|
||||
log(String msg, [bool noTick = false]) {
|
||||
if (!noTick) {
|
||||
tick();
|
||||
}
|
||||
logs.add(msg);
|
||||
}
|
||||
|
||||
clear() => logs.clear();
|
||||
|
||||
tick() => new Future(() {});
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class Hero {
|
||||
String name;
|
||||
Hero(this.name);
|
||||
|
||||
Map toJson() => {'name': name};
|
||||
}
|
||||
|
||||
@Component(
|
||||
selector: 'my-hero',
|
||||
template: '''
|
||||
<div class="hero">
|
||||
<p>{{hero.name}} can {{power}}</p>
|
||||
|
||||
<h4>-- Change Log --</h4>
|
||||
<div *ngFor="#chg of changeLog">{{chg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
|
||||
'p {background: Yellow; padding: 8px; margin-top: 8px}'
|
||||
])
|
||||
class MyHeroComponent implements OnChanges {
|
||||
@Input() Hero hero;
|
||||
@Input() String power;
|
||||
@Input() bool reset;
|
||||
List<String> changeLog = [];
|
||||
|
||||
ngOnChanges(Map<String, SimpleChange> changes) {
|
||||
// Empty the changeLog whenever 'reset' property changes
|
||||
// hint: this is a way to respond programmatically to external value changes.
|
||||
if (changes.containsKey('reset')) changeLog.clear();
|
||||
|
||||
changes.forEach((String key, SimpleChange change) {
|
||||
String cur = JSON.encode(change.currentValue);
|
||||
String prev =
|
||||
change.isFirstChange() ? "{}" : JSON.encode(change.previousValue);
|
||||
changeLog.add('$key: currentValue = ${cur}, previousValue = $prev');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Component(
|
||||
selector: 'on-changes-parent',
|
||||
template: '''
|
||||
<div class="parent">
|
||||
<h2>OnChanges</h2>
|
||||
|
||||
<div>Hero.name: <input [(ngModel)]="hero.name"> <i>does NOT trigger onChanges</i></div>
|
||||
<div>Power: <input [(ngModel)]="power"> <i>DOES trigger onChanges</i></div>
|
||||
<div><button (click)="reset()">Reset Log</button> <i>triggers onChanges and clears the change log</i></div>
|
||||
|
||||
<my-hero [hero]="hero" [power]="power" [reset]="resetTrigger"></my-hero>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.parent {background: Lavender; padding: 10px; margin:100px 8px;}'
|
||||
],
|
||||
directives: const [MyHeroComponent])
|
||||
class OnChangesParentComponent {
|
||||
Hero hero;
|
||||
String power;
|
||||
bool resetTrigger = false;
|
||||
|
||||
OnChangesParentComponent() {
|
||||
reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
// new Hero object every time; triggers onChange
|
||||
hero = new Hero('Windstorm');
|
||||
// setting power only triggers onChange if this value is different
|
||||
power = 'sing';
|
||||
// always triggers onChange ... which is interpreted as a reset
|
||||
resetTrigger = !resetTrigger;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// #docregion
|
||||
// #docregion lc-imports
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'package:lifecycle_hooks/logger_service.dart';
|
||||
|
||||
int nextId = 1;
|
||||
|
||||
@Component(
|
||||
selector: 'peek-a-boo',
|
||||
template: '<p>Now you see my hero, {{name}}</p>',
|
||||
styles: const ['p {background: LightYellow; padding: 8px}'])
|
||||
class PeekABooComponent
|
||||
implements
|
||||
OnChanges,
|
||||
OnInit,
|
||||
AfterContentInit,
|
||||
AfterContentChecked,
|
||||
AfterViewInit,
|
||||
AfterViewChecked,
|
||||
OnDestroy {
|
||||
@Input() String name;
|
||||
|
||||
int _afterContentCheckedCounter = 1;
|
||||
int _afterViewCheckedCounter = 1;
|
||||
int _id = nextId++;
|
||||
LoggerService _logger;
|
||||
int _onChangesCounter = 1;
|
||||
String _verb = 'initialized';
|
||||
|
||||
PeekABooComponent(this._logger);
|
||||
|
||||
// Only called if there is an @input variable set by parent.
|
||||
ngOnChanges(Map<String, SimpleChange> changes) {
|
||||
List<String> messages = [];
|
||||
changes.forEach((String propName, SimpleChange change) {
|
||||
if (propName == 'name') {
|
||||
var name = changes['name'].currentValue;
|
||||
messages.add('name $_verb to "$name"');
|
||||
} else {
|
||||
messages.add('$propName $_verb');
|
||||
}
|
||||
});
|
||||
_logIt('onChanges (${_onChangesCounter++}): ${messages.join('; ')}');
|
||||
_verb = 'changed'; // Next time it will be a change
|
||||
}
|
||||
|
||||
ngOnInit() => _logIt('onInit');
|
||||
|
||||
ngAfterContentInit() => _logIt('afterContentInit');
|
||||
|
||||
// Called after every change detection check
|
||||
// of the component (directive) CONTENT
|
||||
// Beware! Called frequently!
|
||||
ngAfterContentChecked() {
|
||||
int counter = _afterContentCheckedCounter++;
|
||||
_logIt('afterContentChecked (${counter})');
|
||||
}
|
||||
|
||||
ngAfterViewInit() => _logIt('afterViewInit');
|
||||
|
||||
// Called after every change detection check
|
||||
// of the component (directive) VIEW
|
||||
// Beware! Called frequently!
|
||||
ngAfterViewChecked() {
|
||||
int counter = _afterViewCheckedCounter++;
|
||||
_logIt('afterViewChecked ($counter)');
|
||||
}
|
||||
|
||||
ngOnDestroy() => _logIt('onDestroy');
|
||||
|
||||
_logIt(String msg) {
|
||||
// Don't tick or else
|
||||
// the AfterContentChecked and AfterViewChecked recurse.
|
||||
// Let parent call tick()
|
||||
_logger.log("#$_id $msg", true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'package:lifecycle_hooks/logger_service.dart';
|
||||
import 'package:lifecycle_hooks/peek_a_boo_component.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'peek-a-boo-parent',
|
||||
template: '''
|
||||
<div class="parent">
|
||||
<h2>Peek-A-Boo</h2>
|
||||
|
||||
<button (click)="toggleChild()">
|
||||
{{hasChild ? 'Destroy' : 'Create'}} PeekABooComponent
|
||||
</button>
|
||||
<button (click)="updateHero()" [hidden]="!hasChild">Update Hero</button>
|
||||
|
||||
<peek-a-boo *ngIf="hasChild" [name]="heroName">
|
||||
</peek-a-boo>
|
||||
|
||||
<h4>-- Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="#msg of hookLog">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.parent {background: moccasin; padding: 10px; margin:100px 8px}'
|
||||
],
|
||||
directives: const [PeekABooComponent],
|
||||
providers: const [LoggerService])
|
||||
class PeekABooParentComponent {
|
||||
bool hasChild = false;
|
||||
List<String> hookLog;
|
||||
|
||||
String heroName = 'Windstorm';
|
||||
LoggerService _logger;
|
||||
|
||||
PeekABooParentComponent(this._logger) {
|
||||
hookLog = _logger.logs;
|
||||
}
|
||||
|
||||
toggleChild() {
|
||||
hasChild = !hasChild;
|
||||
if (hasChild) {
|
||||
heroName = 'Windstorm';
|
||||
_logger.clear(); // clear log on create
|
||||
}
|
||||
_logger.tick();
|
||||
}
|
||||
|
||||
updateHero() {
|
||||
heroName += '!';
|
||||
_logger.tick();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'logger_service.dart';
|
||||
import 'spy_directive.dart';
|
||||
|
||||
@Component(
|
||||
selector: 'spy-parent',
|
||||
template: '''
|
||||
<div class="parent">
|
||||
<h2>Spy Directive</h2>
|
||||
|
||||
<input [(ngModel)]="newName" (keyup.enter)="addHero()">
|
||||
<button (click)="addHero()">Add Hero</button>
|
||||
<button (click)="reset()">Reset Heroes</button>
|
||||
|
||||
<p></p>
|
||||
<div *ngFor="#hero of heroes" mySpy class="heroes">
|
||||
{{hero}}
|
||||
</div>
|
||||
|
||||
<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||
<div *ngFor="#msg of spyLog">{{msg}}</div>
|
||||
</div>
|
||||
''',
|
||||
styles: const [
|
||||
'.parent {background: khaki; padding: 10px; margin:100px 8px}',
|
||||
'.heroes {background: LightYellow; padding: 0 8px}'
|
||||
],
|
||||
directives: const [Spy],
|
||||
providers: const [LoggerService])
|
||||
class SpyParentComponent {
|
||||
String newName = 'Herbie';
|
||||
List<String> heroes = ['Windstorm', 'Magneta'];
|
||||
List<String> spyLog;
|
||||
LoggerService _logger;
|
||||
|
||||
SpyParentComponent(this._logger) {
|
||||
spyLog = _logger.logs;
|
||||
}
|
||||
|
||||
addHero() {
|
||||
if (newName.trim().isNotEmpty) {
|
||||
heroes.add(newName.trim());
|
||||
newName = '';
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
_logger.log('-- reset --');
|
||||
heroes.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// #docregion
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'logger_service.dart';
|
||||
|
||||
int nextId = 1;
|
||||
|
||||
// Spy on any element to which it is applied.
|
||||
// Usage: <div mySpy>...</div>
|
||||
@Directive(selector: '[mySpy]')
|
||||
class Spy implements OnInit, OnDestroy {
|
||||
int _id = nextId++;
|
||||
LoggerService _logger;
|
||||
|
||||
Spy(this._logger);
|
||||
|
||||
ngOnInit() => _logIt('onInit');
|
||||
|
||||
ngOnDestroy() => _logIt('onDestroy');
|
||||
|
||||
_logIt(String msg) => _logger.log('Spy #$_id $msg');
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
name: lifecycle_hooks
|
||||
description: Lifecycle Hooks
|
||||
version: 0.0.1
|
||||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.1
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^0.1.0
|
||||
transformers:
|
||||
- angular2:
|
||||
platform_directives:
|
||||
- 'package:angular2/common.dart#CORE_DIRECTIVES'
|
||||
- 'package:angular2/common.dart#FORM_DIRECTIVES'
|
||||
entry_points: web/main.dart
|
||||
- dart_to_js_script_rewriter
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- #docregion -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Angular 2 Lifecycle Hooks</title>
|
||||
<script defer src="main.dart" type="application/dart"></script>
|
||||
<script defer src="packages/browser/dart.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,7 @@
|
|||
// #docregion
|
||||
import 'package:angular2/bootstrap.dart';
|
||||
import 'package:lifecycle_hooks/app_component.dart';
|
||||
|
||||
main() {
|
||||
bootstrap(AppComponent);
|
||||
}
|
Loading…
Reference in New Issue