├── skawa_logo.png ├── skawa_components ├── lib │ ├── list_wrapper │ │ ├── list_wrapper.scss │ │ ├── list_wrapper.html │ │ └── list_wrapper.dart │ ├── ckeditor │ │ ├── ckeditor.html │ │ ├── ckeditor_interop.dart │ │ └── ckeditor.dart │ ├── markdown_renderer │ │ ├── markdown_renderer.scss │ │ ├── markdown_renderer.html │ │ ├── raw_markdown_renderer.dart │ │ └── markdown_renderer.dart │ ├── diff │ │ ├── diff.scss │ │ ├── diff.html │ │ └── dff.dart │ ├── sidebar_item │ │ ├── sidebar_item.html │ │ ├── sidebar_item.scss │ │ └── sidebar_item.dart │ ├── code_mirror │ │ ├── code_mirror_mode.dart │ │ ├── code_mirror.html │ │ ├── code_mirror.scss │ │ ├── code_mirror_interop.dart │ │ ├── code_mirror_script_loader.dart │ │ ├── code_mirror_mode_with_link.dart │ │ ├── code_mirror.dart │ │ └── base_loader.dart │ ├── master_detail │ │ ├── master_detail.html │ │ ├── master_detail.scss │ │ └── master_detail.dart │ ├── skawa_components.dart │ ├── nav_item │ │ ├── nav_item.html │ │ ├── nav_item.scss │ │ └── nav_item.dart │ ├── infobar │ │ ├── infobar.html │ │ ├── infobar.scss │ │ └── infobar.dart │ ├── prompt │ │ ├── prompt.html │ │ └── prompt.dart │ ├── markdown_editor │ │ ├── markdown_editor.html │ │ ├── markdown_editor.scss │ │ ├── editor_render_target.dart │ │ └── editor_render_source.dart │ ├── directives │ │ └── language_direction_directive.dart │ ├── css │ │ ├── _material-scrollbar.scss │ │ └── _skawa-material.scss │ ├── util │ │ └── attribute.dart │ ├── pipes │ │ └── hex_colorize_pipe.dart │ └── feature_toggle │ │ └── feature_toggle.dart ├── example │ └── README.md ├── test │ ├── dartlogo │ │ ├── dartlogo.png │ │ ├── config.js │ │ └── plugin.js │ ├── infobar_test.html │ ├── nav_item_test.html │ ├── sidebar_item_test.html │ ├── editor_artifacts.dart │ ├── ckeditor_test.html │ ├── hex_colorize_pipe_test.dart │ ├── ckeditor_test.dart │ ├── sidebar_item_test.dart │ ├── nav_item_test.dart │ ├── master_detail_test.dart │ ├── prompt_test.dart │ ├── infobar_test.dart │ ├── editor_render_target_test.dart │ ├── markdown_editor_test.dart │ ├── language_direction_test.dart │ └── list_wrapper_test.dart ├── dart_test.yaml ├── LICENSE ├── pubspec.yaml ├── README.md └── CHANGELOG.md ├── skawa_material_components ├── lib │ ├── extended_material_icon │ │ ├── extended_material_icon.html │ │ ├── extended_material_icon.scss │ │ └── extended_material_icon.dart │ ├── grid │ │ ├── grid_component.scss │ │ └── grid_component.dart │ ├── data_table │ │ ├── row_data.dart │ │ ├── sort.dart │ │ ├── data_table.html │ │ ├── data_table.scss │ │ └── data_table_column.dart │ ├── skawa_materialish_components.dart │ ├── snackbar │ │ ├── snackbar.html │ │ ├── snackbar.scss │ │ └── snackbar.dart │ ├── card │ │ ├── card.scss │ │ ├── card_content.scss │ │ ├── card_actions.scss │ │ ├── _card_overflow.scss │ │ ├── card_overflow.dart │ │ ├── card_directives.dart │ │ ├── card_actions.dart │ │ ├── card_header.scss │ │ └── card.dart │ ├── skawa_banner │ │ ├── skawa_banner.dart │ │ └── src │ │ │ ├── skawa_banner.html │ │ │ ├── skawa_banner.dart │ │ │ ├── skawa_banner_service.dart │ │ │ └── skawa_banner.scss │ ├── css │ │ ├── _material-scrollbar.scss │ │ ├── _responsiveness.scss │ │ └── _skawa-material.scss │ ├── util │ │ └── attribute.dart │ └── base_implementations │ │ └── grid │ │ └── grid.dart ├── example │ └── README.md ├── test │ ├── skawa_banner │ │ ├── skawa_banner_host │ │ │ ├── skawa_banner_host.scss │ │ │ ├── skawa_banner_host_po.dart │ │ │ └── skawa_banner_host.dart │ │ └── skawa_banner_po.dart │ ├── base_grid_test.html │ ├── data_table_po │ │ ├── test_po.dart │ │ └── data_table_po.dart │ ├── snackbar_test_po.dart │ ├── card_po.dart │ ├── grid_test.dart │ ├── base_grid_test.dart │ └── snackbar_test.dart ├── dart_test.yaml ├── pubspec.yaml ├── LICENSE ├── README.md └── CHANGELOG.md ├── .gitignore ├── .travis.yml ├── tool └── travis.sh ├── README.md └── analysis_options.yaml /skawa_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skawa-universe/skawa_components/HEAD/skawa_logo.png -------------------------------------------------------------------------------- /skawa_components/lib/list_wrapper/list_wrapper.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | overflow-y: auto; 3 | height: 100%; 4 | display: block; 5 | } -------------------------------------------------------------------------------- /skawa_components/lib/ckeditor/ckeditor.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /skawa_components/lib/list_wrapper/list_wrapper.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /skawa_material_components/lib/extended_material_icon/extended_material_icon.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skawa_components/example/README.md: -------------------------------------------------------------------------------- 1 | ## Getting started 2 | 3 | See [examples](https://github.com/skawa-universe/skawa_components_example) 4 | 5 | -------------------------------------------------------------------------------- /skawa_components/test/dartlogo/dartlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skawa-universe/skawa_components/HEAD/skawa_components/test/dartlogo/dartlogo.png -------------------------------------------------------------------------------- /skawa_components/lib/markdown_renderer/markdown_renderer.scss: -------------------------------------------------------------------------------- 1 | @import "../markdown_editor/markdown_style"; 2 | 3 | :host { 4 | @include md_style; 5 | } 6 | -------------------------------------------------------------------------------- /skawa_material_components/example/README.md: -------------------------------------------------------------------------------- 1 | ## Getting started 2 | 3 | See [examples](https://github.com/skawa-universe/skawa_components_example) 4 | 5 | -------------------------------------------------------------------------------- /skawa_components/lib/diff/diff.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | ins { 3 | background-color: #e6ffe6; 4 | } 5 | 6 | del { 7 | background-color: #ffe6e6 8 | } 9 | } -------------------------------------------------------------------------------- /skawa_components/test/dartlogo/config.js: -------------------------------------------------------------------------------- 1 | CKEDITOR.editorConfig = function (config) { 2 | config.language = "en"; 3 | config.extraPlugins = 'dartlogo'; 4 | config.extraAllowedContent = "img{src};"; 5 | } -------------------------------------------------------------------------------- /skawa_components/dart_test.yaml: -------------------------------------------------------------------------------- 1 | # Define tags we expect for this test suite. 2 | tags: 3 | aot: 4 | platform: browser 5 | 6 | override_platforms: 7 | chrome: 8 | settings: 9 | arguments: --no-sandbox -------------------------------------------------------------------------------- /skawa_material_components/test/skawa_banner/skawa_banner_host/skawa_banner_host.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | width: 600px; 3 | height: 200px; 4 | 5 | div.header { 6 | font-size: 16px; 7 | padding: 24px 0; 8 | } 9 | } -------------------------------------------------------------------------------- /skawa_material_components/dart_test.yaml: -------------------------------------------------------------------------------- 1 | # Define tags we expect for this test suite. 2 | tags: 3 | aot: 4 | platform: browser 5 | 6 | override_platforms: 7 | chrome: 8 | settings: 9 | arguments: --no-sandbox -------------------------------------------------------------------------------- /skawa_components/lib/sidebar_item/sidebar_item.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /skawa_components/lib/markdown_renderer/markdown_renderer.html: -------------------------------------------------------------------------------- 1 |
6 |
-------------------------------------------------------------------------------- /skawa_components/lib/code_mirror/code_mirror_mode.dart: -------------------------------------------------------------------------------- 1 | class CodeMirrorMode { 2 | final String mode; 3 | final String modeName; 4 | final String linter; 5 | final String theme; 6 | 7 | const CodeMirrorMode({this.mode, this.modeName, this.linter, this.theme = 'eclipse'}); 8 | } 9 | -------------------------------------------------------------------------------- /skawa_components/lib/master_detail/master_detail.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |
7 | -------------------------------------------------------------------------------- /skawa_components/lib/diff/diff.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skawa_material_components/lib/grid/grid_component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | .grid-container { 4 | position: relative; 5 | ::ng-deep [gridTile] { 6 | position: absolute; 7 | width: 280px; 8 | margin: 16px; 9 | transition: transform 0.3s ease-out; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /skawa_components/lib/skawa_components.dart: -------------------------------------------------------------------------------- 1 | library skawa_components; 2 | 3 | import 'ckeditor/ckeditor.dart'; 4 | import 'infobar/infobar.dart'; 5 | 6 | export 'ckeditor/ckeditor.dart'; 7 | export 'infobar/infobar.dart'; 8 | 9 | const List skawaDirectives = const [SkawaInfobarComponent, SkawaCkeditorComponent]; 10 | -------------------------------------------------------------------------------- /skawa_material_components/lib/data_table/row_data.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// The SkawaDataTableComponent can display objects extending this abstract class. 3 | /// 4 | abstract class RowData { 5 | bool checked = false; 6 | bool disabled; 7 | List classes; 8 | 9 | RowData(this.checked, {this.classes = const [], this.disabled = false}); 10 | } 11 | -------------------------------------------------------------------------------- /skawa_components/lib/nav_item/nav_item.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /skawa_material_components/test/base_grid_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Custom HTML Test 6 | 7 | 8 | 9 | 10 | // ... 11 | 12 | 13 | -------------------------------------------------------------------------------- /skawa_components/test/infobar_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom HTML Test 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /skawa_components/test/nav_item_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom HTML Test 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /skawa_components/test/sidebar_item_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom HTML Test 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /skawa_material_components/test/data_table_po/test_po.dart: -------------------------------------------------------------------------------- 1 | import 'package:pageloader/pageloader.dart'; 2 | 3 | import 'data_table_po.dart'; 4 | 5 | part 'test_po.g.dart'; 6 | 7 | @PageObject() 8 | @CheckTag('test') 9 | abstract class TestPO { 10 | TestPO(); 11 | 12 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 13 | 14 | @ByTagName('skawa-data-table') 15 | DatatablePO get dataTable; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /skawa_material_components/lib/skawa_materialish_components.dart: -------------------------------------------------------------------------------- 1 | library skawa_material_components; 2 | 3 | import 'card/card.dart'; 4 | import 'data_table/data_table.dart'; 5 | 6 | export 'card/card.dart'; 7 | export 'data_table/data_table.dart'; 8 | export 'data_table/data_table_column.dart'; 9 | export 'data_table/row_data.dart'; 10 | 11 | List skawaMaterialDirectives = [skawaCardDirectives, skawaDataTableDirectives]; 12 | -------------------------------------------------------------------------------- /skawa_material_components/lib/snackbar/snackbar.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
-------------------------------------------------------------------------------- /skawa_material_components/lib/card/card.scss: -------------------------------------------------------------------------------- 1 | @import "package:skawa_material_components/css/skawa-material"; 2 | 3 | :host { 4 | display: block; 5 | border-radius: 2px; 6 | box-sizing: border-box; 7 | background-color: white; 8 | @include shadow-2dp; 9 | &[no-shadow] { 10 | box-shadow: none; 11 | } 12 | &.with-actions { 13 | display: flex; 14 | ::ng-deep skawa-header-title { 15 | flex-grow: 1; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don’t commit the following directories created by pub. 2 | .buildlog 3 | .pub/ 4 | build/ 5 | doc 6 | packages 7 | .packages 8 | 9 | # Or the files created by dart2js. 10 | *.dart.js 11 | *.js_ 12 | *.js.deps 13 | *.js.map 14 | *.g.dart 15 | 16 | # Include when developing application packages. 17 | pubspec.lock 18 | 19 | # Win/Mac garbage 20 | .DS_Store 21 | Thumbs.db 22 | 23 | #IDE garbage 24 | .dart_tool 25 | .idea 26 | skawa_components.iml 27 | -------------------------------------------------------------------------------- /skawa_material_components/lib/card/card_content.scss: -------------------------------------------------------------------------------- 1 | @import "package:skawa_material_components/css/skawa-material"; 2 | 3 | :host { 4 | @include typo-body; 5 | display: block; 6 | padding: 16px; 7 | &.with-header { 8 | padding-top: 0; 9 | } 10 | // TODO: do not use attribs just for styling 11 | &[fullWidth], &.skawa-full-width { 12 | padding: 0; 13 | } 14 | &.skawa-collapsed { 15 | display: none; 16 | color: green; 17 | } 18 | } -------------------------------------------------------------------------------- /skawa_components/test/editor_artifacts.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | import 'package:mockito/mockito.dart'; 3 | import 'package:skawa_components/markdown_editor/editor_render_target.dart'; 4 | 5 | class MockRenderer extends Mock implements EditorRenderer { 6 | MockRenderer() { 7 | print('created'); 8 | when(this.render('')).thenAnswer((Invocation inv) { 9 | var str = inv.positionalArguments[0]; 10 | return DocumentFragment.html('$str'); 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /skawa_components/lib/infobar/infobar.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
-------------------------------------------------------------------------------- /skawa_components/test/ckeditor_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /skawa_components/lib/code_mirror/code_mirror.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /skawa_components/lib/diff/dff.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | import 'package:diff_match_patch/diff_match_patch.dart'; 3 | 4 | /// This is an angular wrapper around the `https://github.com/jheyne/diff-match-patch` 5 | 6 | @Component( 7 | selector: 'diff', 8 | templateUrl: 'diff.html', 9 | styleUrls: ['diff.css'], 10 | directives: [NgIf, NgFor], 11 | exports: [DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT], 12 | changeDetection: ChangeDetectionStrategy.OnPush) 13 | class DiffComponent { 14 | @Input() 15 | List diffList; 16 | } 17 | -------------------------------------------------------------------------------- /skawa_material_components/lib/skawa_banner/skawa_banner.dart: -------------------------------------------------------------------------------- 1 | library skawa_banner; 2 | 3 | import 'dart:async'; 4 | import 'dart:html'; 5 | 6 | import 'package:angular/angular.dart'; 7 | import 'package:angular_components/material_button/material_button.dart'; 8 | import 'package:angular_components/material_icon/material_icon.dart'; 9 | import 'package:angular_components/utils/disposer/disposer.dart'; 10 | import 'package:intl/intl.dart'; 11 | 12 | part 'src/skawa_banner.dart'; 13 | part 'src/skawa_banner_message.dart'; 14 | part 'src/skawa_banner_service.dart'; 15 | -------------------------------------------------------------------------------- /skawa_components/lib/nav_item/nav_item.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | font-size: 15px; 4 | font-weight: normal; 5 | position: relative; // needed by ripple 6 | background: transparent; 7 | border-radius: inherit; 8 | box-sizing: border-box; 9 | cursor: pointer; 10 | letter-spacing: .01em; 11 | line-height: normal; 12 | outline: none; 13 | text-align: center; 14 | z-index: 0; 15 | transition: color 0.4s linear, background-color 0.4s linear; 16 | &:hover { 17 | color: rgba(0,0,0,.987); 18 | background-color: rgba(0,0,0,.05); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /skawa_components/lib/master_detail/master_detail.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | .hidden { 4 | visibility: hidden; 5 | } 6 | &[expanded] { 7 | .master-detail--detail { 8 | opacity: 1; 9 | visibility: visible; 10 | max-height: 100%; 11 | transition: opacity 436ms cubic-bezier(0.4, 0, 0.2, 1); 12 | } 13 | } 14 | 15 | .master-detail--detail { 16 | opacity: 0; 17 | visibility: hidden; 18 | max-height: 0; 19 | transition: visibility 0s linear 0.5s, max-height 0s linear 0.5s, opacity 436ms cubic-bezier(0.4, 0, 0.2, 1); 20 | } 21 | } -------------------------------------------------------------------------------- /skawa_components/lib/code_mirror/code_mirror.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | ::ng-deep .CodeMirror { 4 | border-bottom: 2px solid #3f51b5; 5 | border-top: 2px solid #3f51b5; 6 | border-right: 2px solid #3f51b5; 7 | padding-right: 8px; 8 | } 9 | ::ng-deep .CodeMirror-gutters { 10 | background-color: #3f51b5; 11 | } 12 | ::ng-deep .CodeMirror-linenumber { 13 | color: white; 14 | } 15 | .error-row { 16 | display: flex; 17 | align-items: center; 18 | .warning-icon { 19 | color: rgb(251, 192, 45); 20 | } 21 | .error-icon { 22 | color: rgb(255, 0, 0); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /skawa_components/lib/prompt/prompt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | {{message}} 6 |

7 |
8 | 13 | 14 |
15 |
16 |
-------------------------------------------------------------------------------- /skawa_components/lib/markdown_renderer/raw_markdown_renderer.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | import 'package:skawa_components/directives/language_direction_directive.dart'; 3 | import 'package:skawa_components/markdown_editor/editor_render_target.dart'; 4 | import 'package:skawa_components/markdown_renderer/markdown_renderer.dart'; 5 | 6 | @Component( 7 | selector: 'skawa-raw-markdown-renderer', 8 | templateUrl: 'markdown_renderer.html', 9 | styles: [':host{display:block}'], 10 | directives: [EditorRenderTarget, LanguageDirectionDirective], 11 | changeDetection: ChangeDetectionStrategy.OnPush) 12 | class SkawaRawMarkdownRendererComponent extends SkawaMarkdownRendererComponent {} 13 | -------------------------------------------------------------------------------- /skawa_material_components/lib/card/card_actions.scss: -------------------------------------------------------------------------------- 1 | @import "package:angular_components/css/material/material"; 2 | 3 | :host { 4 | display: flex; 5 | padding: 8px; 6 | &[align-right] { 7 | justify-content: flex-end; 8 | } 9 | &[align-left] { 10 | justify-content: flex-start; 11 | } 12 | ::ng-deep [spacer], ::ng-deep .spacer { 13 | display: block; 14 | flex-grow: 1; 15 | } 16 | } 17 | 18 | :host-context(.with-actions) { 19 | padding: 0; 20 | ::ng-deep material-button[icon] { 21 | //color: $secondary-text-color; 22 | color: $mat-indigo-400; 23 | // spacing between buttons should be 16px 24 | // https://material.io/guidelines/components/data-tables.html#data-tables-specs 25 | margin-left: 8px; 26 | } 27 | } -------------------------------------------------------------------------------- /skawa_components/lib/infobar/infobar.scss: -------------------------------------------------------------------------------- 1 | @import "../css/skawa-material"; 2 | 3 | :host { 4 | @include typo-caption; 5 | display: flex; 6 | align-items: center; 7 | height: 40px; 8 | box-shadow: -4px 0 0 0 #d3d3d3, 0 1px 3px rgba(0, 0, 0, 0.12); 9 | border-radius: 2px; 10 | border: 1px solid #e7e7e7; 11 | border-left: 0; 12 | margin-bottom: 16px; 13 | background-color: white; 14 | .hide-without-icon { 15 | display: none; 16 | } 17 | .without-url { 18 | cursor: default; 19 | } 20 | .info-text { 21 | &.without-icon { 22 | padding-left: 16px; 23 | } 24 | display: inline-flex; 25 | .ws-truncate-fix { 26 | @include ws-truncate-fix; 27 | } 28 | } 29 | ::ng-deep material-button { 30 | display: inline-flex; 31 | } 32 | } -------------------------------------------------------------------------------- /skawa_components/lib/sidebar_item/sidebar_item.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | padding: 6px 0; 3 | box-sizing: border-box; 4 | 5 | display: flex; 6 | align-items: center; 7 | height: 44px; 8 | color: rgba(0,0,0,.654); 9 | flex-grow: 1; 10 | transition: color 0.4s linear; 11 | flex-basis: 100%; 12 | .item-icon { 13 | width: 64px; 14 | justify-content: center; 15 | } 16 | .item-text { 17 | flex-grow: 1; 18 | line-height: 24px; 19 | text-align: left; 20 | &.text-only { 21 | text-transform: uppercase; 22 | font-size: 12px; 23 | } 24 | } 25 | &:hover, &.hovering { 26 | color: rgba(0, 0, 0, 0.998); 27 | } 28 | 29 | &[textOnly] { 30 | padding-left: 20px; 31 | } 32 | &.icon-padding { 33 | padding-left: 64px; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /skawa_material_components/lib/card/_card_overflow.scss: -------------------------------------------------------------------------------- 1 | @mixin card-overflow($background-color: white, $start: 20%, $end: 80%) { 2 | .card-overflowed:after { 3 | display: block; 4 | position: absolute; 5 | content: ""; 6 | top: 0; 7 | left: 0; 8 | width: 100%; 9 | height: 100%; 10 | background: linear-gradient(to bottom, rgba($background-color, 0) $start, rgba($background-color, 1) $end); 11 | pointer-events: none; 12 | } 13 | 14 | .card-overflowed-full-card:after { 15 | display: block; 16 | position: absolute; 17 | content: ""; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | background: linear-gradient(to bottom, rgba($background-color, 0) 80%, rgba($background-color, 1) 100%); 23 | pointer-events: none; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /skawa_components/lib/markdown_editor/markdown_editor.html: -------------------------------------------------------------------------------- 1 |
9 |
10 | 11 | 19 | 20 | -------------------------------------------------------------------------------- /skawa_components/lib/directives/language_direction_directive.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | import 'package:angular/core.dart'; 3 | import 'package:intl/intl.dart' as intl; 4 | 5 | @Directive(selector: "[skawaLangugageDirection]", exportAs: 'skawaLangugageDirection') 6 | class LanguageDirectionDirective { 7 | final HtmlElement _htmlElement; 8 | 9 | @HostBinding('style.direction') 10 | String textDirection = 'ltr'; 11 | 12 | @HostBinding('style.text-align') 13 | String textAlign; 14 | 15 | LanguageDirectionDirective(this._htmlElement); 16 | 17 | bool get _elementIsInput => _htmlElement is InputElement || _htmlElement is TextAreaElement; 18 | 19 | void setLanguageDirection(String value) { 20 | textDirection = intl.Bidi.estimateDirectionOfText(value ?? '').spanText; 21 | textAlign = _elementIsInput || textDirection == 'ltr' ? null : 'right'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /skawa_components/test/dartlogo/plugin.js: -------------------------------------------------------------------------------- 1 | CKEDITOR.plugins.add('dartlogo', 2 | { 3 | requires: ['iframedialog'], 4 | init: function (editor) { 5 | var pluginName = 'dartlog'; 6 | var mypath = this.path; 7 | editor.ui.addButton( 8 | 'dartlogo.btn', 9 | { 10 | label: "Dart logo inserted", 11 | command: 'dartlogo.cmd', 12 | icon: mypath + 'dartlogo.png' 13 | } 14 | ); 15 | var cmd = editor.addCommand('dartlogo.cmd', {exec: showDialogPlugin}); 16 | } 17 | } 18 | ); 19 | 20 | function showDialogPlugin(editor) { 21 | var imageElement = editor.document.createElement('img'); 22 | imageElement.setAttribute("src", 'dartlogo/dartlogo.png'); 23 | editor.insertElement(imageElement); 24 | } -------------------------------------------------------------------------------- /skawa_material_components/lib/extended_material_icon/extended_material_icon.scss: -------------------------------------------------------------------------------- 1 | $x-small: 12px; 2 | $small: 13px; 3 | $medium: 16px; 4 | $large: 18px; 5 | $x-large: 20px; 6 | $default: 24px; 7 | 8 | // Local size method which is on elements which don't need deep. 9 | @mixin _material-icon-size($size) { 10 | .mdi { 11 | font-size: $size; 12 | } 13 | } 14 | 15 | :host { 16 | display: inline-flex; 17 | 18 | @include _material-icon-size($default); 19 | 20 | &[size="x-small"] { 21 | @include _material-icon-size($x-small); 22 | } 23 | 24 | &[size="small"] { 25 | @include _material-icon-size($small); 26 | } 27 | 28 | &[size="medium"] { 29 | @include _material-icon-size($medium); 30 | } 31 | 32 | &[size="large"] { 33 | @include _material-icon-size($large); 34 | } 35 | 36 | &[size="x-large"] { 37 | @include _material-icon-size($x-large); 38 | } 39 | } -------------------------------------------------------------------------------- /skawa_components/lib/ckeditor/ckeditor_interop.dart: -------------------------------------------------------------------------------- 1 | @JS() 2 | library ckeditor_interop; 3 | 4 | import 'package:js/js.dart'; 5 | 6 | @JS('CKEDITOR') 7 | abstract class CKEditor { 8 | @JS('replace') 9 | external static CKEditorInstance replace(String element, Object config); 10 | 11 | @JS("config") 12 | external Map get config; 13 | } 14 | 15 | @JS() 16 | class CKEditorInstance { 17 | external String get name; 18 | 19 | external String getData(); 20 | 21 | external void on(String eventName, EventCallback callback); 22 | } 23 | 24 | @JS() 25 | @anonymous 26 | class EventInfo { 27 | external CKEditorInstance get editor; 28 | 29 | external factory EventInfo({CKEditorInstance editor}); 30 | } 31 | 32 | @JS('CKEDITOR.plugins.addExternal') 33 | external void addExternalPlugin(String name, String path, String fileName); 34 | 35 | typedef EventCallback = void Function(EventInfo evt); 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | 3 | dart: 4 | # - dev currently the dartfmt failing on dev 5 | - stable 6 | 7 | addons: 8 | chrome: stable 9 | 10 | before_install: 11 | - chmod ugo+x tool/travis.sh 12 | 13 | # Only building master means that we don't run two builds for each pull request. 14 | branches: 15 | only: [master] 16 | 17 | cache: 18 | directories: 19 | - $HOME/.pub-cache 20 | 21 | stages: 22 | - presubmit 23 | - test 24 | 25 | jobs: 26 | include: 27 | - stage: presubmit 28 | env: PKG="skawa_material_components" 29 | script: ./tool/travis.sh presubmit 30 | - stage: test 31 | env: PKG="skawa_material_components" 32 | script: ./tool/travis.sh test 33 | 34 | - stage: presubmit 35 | env: PKG="skawa_components" 36 | script: ./tool/travis.sh presubmit 37 | - stage: test 38 | env: PKG="skawa_components" 39 | script: ./tool/travis.sh test 40 | -------------------------------------------------------------------------------- /skawa_material_components/lib/skawa_banner/src/skawa_banner.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skawa_material_components/test/snackbar_test_po.dart: -------------------------------------------------------------------------------- 1 | import 'package:pageloader/pageloader.dart'; 2 | 3 | part 'snackbar_test_po.g.dart'; 4 | 5 | @PageObject() 6 | @CheckTag('test') 7 | abstract class TestPO { 8 | TestPO(); 9 | 10 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 11 | 12 | @Global(ByClass('snackbar-container')) 13 | SnackbarPO get snackbar; 14 | 15 | @ByTagName('span') 16 | PageLoaderElement get messageSpan; 17 | 18 | @ByTagName('p') 19 | PageLoaderElement get callbackP; 20 | } 21 | 22 | @PageObject() 23 | abstract class SnackbarPO { 24 | SnackbarPO(); 25 | 26 | factory SnackbarPO.create(PageLoaderElement context) = $SnackbarPO.create; 27 | 28 | @root 29 | PageLoaderElement get rootElement; 30 | 31 | @ByTagName('span') 32 | PageLoaderElement get messageContainer; 33 | 34 | @ByTagName('material-button') 35 | PageLoaderElement get materialButton; 36 | } 37 | -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fast fail the script on failures. 4 | set -e 5 | 6 | # Check arguments. 7 | TASK=$1 8 | 9 | if [ -z "$PKG" ]; then 10 | echo -e '\033[31mPKG argument must be set!\033[0m' 11 | exit 1 12 | fi 13 | 14 | if [ -z "$TASK" ]; then 15 | echo -e '\033[31mTASK argument must be set!\033[0m' 16 | exit 1 17 | fi 18 | 19 | # Navigate to the correct sub-directory, and run "pub upgrade". 20 | pushd $PKG 21 | PUB_ALLOW_PRERELEASE_SDK=quiet pub get 22 | EXIT_CODE=1 23 | 24 | case $TASK in 25 | 26 | presubmit) 27 | dartfmt lib/ --line-length=120 --set-exit-if-changed -n 28 | dartfmt test/ --line-length=120 --set-exit-if-changed -n 29 | pub run build_runner build 30 | dartanalyzer --fatal-warnings --no-hints --no-lints ./lib 31 | ;; 32 | 33 | test) 34 | pub run build_runner test -- -p chrome 35 | ;; 36 | 37 | *) 38 | echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m" 39 | exit 1 40 | ;; 41 | esac -------------------------------------------------------------------------------- /skawa_material_components/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: skawa_material_components 2 | version: 1.5.1 3 | authors: 4 | - Harsányi Tamás (valakis) 5 | - Szepesházi András 6 | - Varga-Hali Daniel 7 | description: 8 | Additional Material Design components for AngularDart. Complements the official `angular_components` package. 9 | homepage: https://github.com/skawa-universe/skawa_components 10 | 11 | environment: 12 | sdk: '>=2.9.0 <3.0.0' 13 | 14 | dependencies: 15 | angular: ^6.0.0 16 | angular_components: ^1.0.0 17 | quiver: ^2.0.3 18 | sass_builder: ^2.1.2 19 | intl: ^0.16.0 20 | 21 | dev_dependencies: 22 | test: ^1.6.2 23 | angular_test: ^3.0.0 24 | pageloader: 25 | git: 26 | url: https://github.com/skawa-universe/pageloader.git 27 | ref: analyzer-40 28 | build_config: ^0.4.1+1 29 | build_test: ^1.0.0 30 | build_runner: ^1.7.0 31 | build_web_compilers: ^2.0.0 32 | -------------------------------------------------------------------------------- /skawa_components/lib/css/_material-scrollbar.scss: -------------------------------------------------------------------------------- 1 | // ?? does this work in FX?, IE? 2 | ::-webkit-scrollbar { 3 | width: 12px; 4 | height: 12px 5 | } 6 | 7 | ::-webkit-scrollbar-button { 8 | height: 0; 9 | width: 0; 10 | } 11 | 12 | ::-webkit-scrollbar-track { 13 | background-color: rgb(250, 250, 250); 14 | } 15 | 16 | ::-webkit-scrollbar-button:start { 17 | display: none; 18 | } 19 | 20 | ::-webkit-scrollbar-button:end{ 21 | display: none; 22 | } 23 | 24 | 25 | ::-webkit-scrollbar-thumb { 26 | background-color: rgba(0,0,0,0.1); 27 | background-clip: border-box; 28 | border: none; 29 | min-height: 28px; 30 | padding: 0 0 0; 31 | box-shadow: inset 1px 1px 0 rgba(0,0,0,0.1),inset 0 -1px 0 rgba(0,0,0,0.07) 32 | } 33 | 34 | ::-webkit-scrollbar-thumb:hover { 35 | background-color: rgba(0,0,0,0.15); 36 | box-shadow: inset 1px 1px 1px rgba(0,0,0,0.25) 37 | } 38 | 39 | ::-webkit-scrollbar-thumb:active { 40 | box-shadow: inset 1px 1px 3px rgba(0,0,0,0.35); 41 | background-color: rgba(0,0,0,0.2) 42 | } -------------------------------------------------------------------------------- /skawa_material_components/lib/css/_material-scrollbar.scss: -------------------------------------------------------------------------------- 1 | // ?? does this work in FX?, IE? 2 | ::-webkit-scrollbar { 3 | width: 12px; 4 | height: 12px 5 | } 6 | 7 | ::-webkit-scrollbar-button { 8 | height: 0; 9 | width: 0; 10 | } 11 | 12 | ::-webkit-scrollbar-track { 13 | background-color: rgb(250, 250, 250); 14 | } 15 | 16 | ::-webkit-scrollbar-button:start { 17 | display: none; 18 | } 19 | 20 | ::-webkit-scrollbar-button:end{ 21 | display: none; 22 | } 23 | 24 | 25 | ::-webkit-scrollbar-thumb { 26 | background-color: rgba(0,0,0,0.1); 27 | background-clip: border-box; 28 | border: none; 29 | min-height: 28px; 30 | padding: 0 0 0; 31 | box-shadow: inset 1px 1px 0 rgba(0,0,0,0.1),inset 0 -1px 0 rgba(0,0,0,0.07) 32 | } 33 | 34 | ::-webkit-scrollbar-thumb:hover { 35 | background-color: rgba(0,0,0,0.15); 36 | box-shadow: inset 1px 1px 1px rgba(0,0,0,0.25) 37 | } 38 | 39 | ::-webkit-scrollbar-thumb:active { 40 | box-shadow: inset 1px 1px 3px rgba(0,0,0,0.35); 41 | background-color: rgba(0,0,0,0.2) 42 | } -------------------------------------------------------------------------------- /skawa_material_components/test/skawa_banner/skawa_banner_host/skawa_banner_host_po.dart: -------------------------------------------------------------------------------- 1 | import 'package:pageloader/pageloader.dart'; 2 | 3 | import '../skawa_banner_po.dart'; 4 | 5 | part 'skawa_banner_host_po.g.dart'; 6 | 7 | @PageObject() 8 | abstract class SkawaBannerHostPO { 9 | SkawaBannerHostPO(); 10 | 11 | factory SkawaBannerHostPO.create(PageLoaderElement context) = $SkawaBannerHostPO.create; 12 | 13 | @root 14 | PageLoaderElement get rootElement; 15 | 16 | @ByCss('div.header') 17 | PageLoaderElement get header; 18 | 19 | @ByCheckTag() 20 | SkawaBannerPO get banner; 21 | 22 | @ByCss('.main-content') 23 | PageLoaderElement get content; 24 | 25 | @ByCss('material-button[raised]') 26 | List get actions; 27 | 28 | Future showWarning() async => await actions[0].click(); 29 | 30 | Future showInfo() async => await actions[1].click(); 31 | 32 | Future showInfoWithoutPriority() async => await actions[2].click(); 33 | 34 | Future showError() async => await actions[3].click(); 35 | } 36 | -------------------------------------------------------------------------------- /skawa_components/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Skawa Ltd. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /skawa_components/lib/util/attribute.dart: -------------------------------------------------------------------------------- 1 | library skawa_components.utils; 2 | 3 | /// Decides if an attribute is present or missing 4 | /// 5 | /// __Example:__ 6 | /// @Component( 7 | /// host: const { '[attr.someInput]': 'someInput' } 8 | /// ) 9 | /// class SomeComponent{ 10 | /// @Input('[attr.someInput]') 11 | /// var someInput; 12 | /// bool get isSomeInput => isPresent(someInput); 13 | /// } 14 | bool isPresent(dynamic input) => input != null; 15 | 16 | /// Toggles attribute 17 | /// 18 | /// __Example:__ 19 | /// 20 | /// @Component(/*...*/) 21 | /// class SomeComponent{ 22 | /// @Input('[attr.someInput]') 23 | /// var someInput; 24 | /// bool get isSomeInput => isPresent(someInput); 25 | /// 26 | /// void toggle() { 27 | /// toggleAttribute(someInput); 28 | /// } 29 | /// } 30 | bool toggleAttribute(bool val) { 31 | // In Angular, an attribute is removed if it's value is `null` and added 32 | // if it's anything else, like 33 | return !isPresent(val) ? true : null; 34 | } 35 | -------------------------------------------------------------------------------- /skawa_material_components/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Skawa Ltd. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /skawa_components/lib/code_mirror/code_mirror_interop.dart: -------------------------------------------------------------------------------- 1 | @JS() 2 | library code_mirror_interop; 3 | 4 | import 'dart:html'; 5 | import 'package:js/js.dart'; 6 | 7 | @JS('CodeMirror') 8 | abstract class CodeMirror { 9 | @JS() 10 | external CodeMirror.fromBody(Element element, Map config); 11 | 12 | @JS('fromTextArea') 13 | external static CodeMirror fromTextArea(Element element, dynamic config); 14 | 15 | external void toTextArea(); 16 | 17 | external String getValue([String separator]); 18 | 19 | external void setValue(String content); 20 | 21 | external void setOption(String content, dynamic value); 22 | 23 | external void showHint(CodeMirror codeMirror, Function function); 24 | 25 | @JS('on') 26 | external void on(String eventName, Function function); 27 | 28 | @JS('off') 29 | external void off(String eventName, Function function); 30 | 31 | @JS('getHelper') 32 | external Object getHelper(String type); 33 | } 34 | 35 | @JS('CodeMirror.hint') 36 | class CodeMirrorHint { 37 | @JS('javascript') 38 | external static void jsHint(); 39 | } 40 | -------------------------------------------------------------------------------- /skawa_material_components/lib/card/card_overflow.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | 3 | import 'package:angular/angular.dart'; 4 | 5 | /// A directive which can be used to hide the overflow with a linear gradient foreground. 6 | /// Need to include `@import "package:skawa_material_components/card/card_overflow";` 7 | /// to your css file to work this directive (also include the corresponding csss mixin). 8 | 9 | @Directive(selector: '[cardOverflow]', exportAs: 'cardOverflow') 10 | class CardOverflowDirective { 11 | final HtmlElement _htmlElement; 12 | 13 | CardOverflowDirective(this._htmlElement); 14 | 15 | @Input('fullCard') 16 | bool fullCard = false; 17 | 18 | @Input('contentLoaded') 19 | bool contentLoaded = false; 20 | 21 | bool get hasOverflow => _htmlElement.clientHeight < _htmlElement.scrollHeight; 22 | 23 | @HostBinding('class.card-overflowed') 24 | bool get excerptHasOverflow => !fullCard && hasOverflow; 25 | 26 | @HostBinding('class.card-overflowed-full-card') 27 | bool get fullCardHasOverflow => fullCard && hasOverflow && contentLoaded; 28 | } 29 | -------------------------------------------------------------------------------- /skawa_material_components/lib/util/attribute.dart: -------------------------------------------------------------------------------- 1 | library skawa_components.utils; 2 | 3 | /// Decides if an attribute is present or missing 4 | /// 5 | /// __Example:__ 6 | /// @Component( 7 | /// host: const { '[attr.someInput]': 'someInput' } 8 | /// ) 9 | /// class SomeComponent{ 10 | /// @Input('[attr.someInput]') 11 | /// var someInput; 12 | /// bool get isSomeInput => isPresent(someInput); 13 | /// } 14 | bool isPresent(dynamic input) => input != null; 15 | 16 | /// Toggles attribute 17 | /// 18 | /// __Example:__ 19 | /// 20 | /// @Component(/*...*/) 21 | /// class SomeComponent{ 22 | /// @Input('[attr.someInput]') 23 | /// var someInput; 24 | /// bool get isSomeInput => isPresent(someInput); 25 | /// 26 | /// void toggle() { 27 | /// toggleAttribute(someInput); 28 | /// } 29 | /// } 30 | bool toggleAttribute(bool val) { 31 | // In Angular, an attribute is removed if it's value is `null` and added 32 | // if it's anything else, like 33 | return !isPresent(val) ? true : null; 34 | } 35 | -------------------------------------------------------------------------------- /skawa_material_components/lib/snackbar/snackbar.scss: -------------------------------------------------------------------------------- 1 | @import 'package:skawa_material_components/css/_responsiveness'; 2 | 3 | @mixin shadow-2dp() { 4 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2), 5 | 0 3px 1px -2px rgba(0, 0, 0, 0.14), 6 | 0 1px 5px 0 rgba(0, 0, 0, 0.12); 7 | } 8 | 9 | .snackbar-container { 10 | @include shadow-2dp; 11 | position: fixed; 12 | left: 50%; 13 | bottom: 0; 14 | transform: translate(-50%, 100%); 15 | font-size: 14px; 16 | z-index: 10000; 17 | height: 48px; 18 | color: white; 19 | background-color: #323232; 20 | display: flex; 21 | padding: 0 24px; 22 | box-sizing: border-box; 23 | border-radius: 2px; 24 | transition: transform 0.5s ease; 25 | 26 | @include mobile() { 27 | height: 96px; 28 | } 29 | @include tablet-only() { 30 | height: 72px; 31 | } 32 | @include desktop() { 33 | height: 48px; 34 | } 35 | 36 | material-button { 37 | align-self: center; 38 | } 39 | 40 | &.show { 41 | transform: translate(-50%, 0); 42 | } 43 | 44 | .message, .action { 45 | align-self: center; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /skawa_components/lib/markdown_editor/markdown_editor.scss: -------------------------------------------------------------------------------- 1 | @import "markdown_style"; 2 | 3 | :host { 4 | display: block; 5 | position: relative; 6 | 7 | &.mode-display { 8 | .markdown-container { 9 | display: block; 10 | } 11 | 12 | textarea { 13 | display: none; 14 | } 15 | } 16 | 17 | &.mode-edit { 18 | .markdown-container { 19 | display: none !important; 20 | } 21 | 22 | textarea { 23 | display: block; 24 | } 25 | } 26 | 27 | .markdown-container { 28 | @include md_style; 29 | min-height: 200px; 30 | cursor: pointer; 31 | 32 | &.disabled { 33 | cursor: default; 34 | } 35 | 36 | &.with-placeholder { 37 | display: flex; 38 | align-items: center; 39 | } 40 | 41 | .placeholder { 42 | color: rgba(0, 0, 0, 0.36); 43 | text-align: center; 44 | font-size: 20px; 45 | width: 100%; 46 | } 47 | } 48 | 49 | textarea { 50 | width: 100%; 51 | border: none; 52 | background-color: #eee; 53 | min-height: 200px; 54 | padding: 8px; 55 | font-size: 14px; 56 | font-family: monospace; 57 | box-sizing: border-box; 58 | } 59 | } -------------------------------------------------------------------------------- /skawa_components/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: skawa_components 2 | version: 1.5.3 3 | authors: 4 | - Harsányi Tamás (valakis) 5 | - Szepesházi András 6 | - Varga-Hali Daniel 7 | - Farmasi Bulcsú 8 | description: 9 | Additional components for AngularDart based on Material Design principles. These reusable components are not part of the official Material Components library. 10 | homepage: https://github.com/skawa-universe/skawa_components 11 | 12 | environment: 13 | sdk: '>=2.9.0 <3.0.0' 14 | 15 | dependencies: 16 | angular: ^6.0.0 17 | angular_components: ^1.0.0 18 | sass_builder: ^2.1.2 19 | markdown: ^3.0.0 20 | js: ^0.6.1+1 21 | intl: ^0.16.0 22 | diff_match_patch: ^0.3.0 23 | meta: ^1.2.2 24 | 25 | dev_dependencies: 26 | html_unescape: ^1.0.1+2 27 | mockito: ^4.0.0 28 | test: ^1.6.2 29 | angular_test: ^3.0.0 30 | pageloader: 31 | git: 32 | url: https://github.com/skawa-universe/pageloader.git 33 | ref: analyzer-40 34 | build_config: ^0.4.1+1 35 | build_test: ^1.0.0 36 | build_runner: ^1.7.0 37 | build_web_compilers: ^2.0.0 38 | -------------------------------------------------------------------------------- /skawa_material_components/lib/card/card_directives.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/core.dart'; 2 | 3 | /// The three following Directive should be used only inside a SkawaCardHeaderComponent. 4 | /// The [SkawaCardHeaderTitleDirective] should contain the title. 5 | /// The [SkawaCardHeaderSubheadDirective] should contain the subheader. 6 | /// The [SkawaCardHeaderImageDirective] should contain an image. 7 | /// 8 | /// __Example:__ 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// The card title. 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | @Directive(selector: 'skawa-header-title') 26 | class SkawaCardHeaderTitleDirective {} 27 | 28 | @Directive(selector: 'skawa-header-subhead') 29 | class SkawaCardHeaderSubheadDirective {} 30 | 31 | @Directive(selector: 'skawa-header-image') 32 | class SkawaCardHeaderImageDirective {} 33 | -------------------------------------------------------------------------------- /skawa_material_components/lib/card/card_actions.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/core.dart'; 2 | 3 | import 'card.dart'; 4 | 5 | /// Card section containing actions. Should be used only inside a [SkawaCardComponent]. 6 | /// 7 | /// __Example:__ 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// __Inputs:__ 18 | /// 19 | /// - `align-left`: Align content elements to the right. 20 | /// - `align-right`: Align content elements to the left. 21 | /// 22 | @Component( 23 | selector: 'skawa-card-actions', 24 | template: '', 25 | styleUrls: ['card_actions.css'], 26 | changeDetection: ChangeDetectionStrategy.OnPush) 27 | class SkawaCardActionsComponent { 28 | /// Header component actions were embedded in 29 | final SkawaCardHeaderComponent cardHeader; 30 | 31 | SkawaCardActionsComponent(@Optional() this.cardHeader); 32 | 33 | /// Whether this component is in a card header or not 34 | @HostBinding('class.in-header') 35 | bool get inHeader => cardHeader != null; 36 | } 37 | -------------------------------------------------------------------------------- /skawa_components/lib/markdown_renderer/markdown_renderer.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | import 'package:skawa_components/directives/language_direction_directive.dart'; 3 | import 'package:skawa_components/markdown_editor/editor_render_target.dart'; 4 | 5 | @Component( 6 | selector: 'skawa-markdown-renderer', 7 | templateUrl: 'markdown_renderer.html', 8 | styleUrls: ['markdown_renderer.css'], 9 | directives: [EditorRenderTarget, LanguageDirectionDirective], 10 | changeDetection: ChangeDetectionStrategy.OnPush) 11 | class SkawaMarkdownRendererComponent implements OnInit { 12 | List _emulatedCssClass; 13 | String _source; 14 | 15 | @ViewChild(EditorRenderTarget) 16 | EditorRenderTarget editorRenderTarget; 17 | 18 | @Input() 19 | set source(String source) { 20 | _source = source ?? ''; 21 | editorRenderTarget.updateRender(_source, classes: _emulatedCssClass); 22 | } 23 | 24 | @override 25 | void ngOnInit() { 26 | Iterable classes = 27 | editorRenderTarget.htmlElement.classes.where((String cssClass) => !cssClass.contains('markdown-container')); 28 | _emulatedCssClass = classes.isNotEmpty ? classes.toList() : []; 29 | editorRenderTarget.updateRender(_source, classes: _emulatedCssClass); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [![Build Status](https://travis-ci.org/skawa-universe/skawa_components.svg?branch=master)](https://travis-ci.org/skawa-universe/skawa_components) 5 | 6 | These components and directives are built with [AngularDart](https://pub.dev/packages/angular), and 7 | based on following the [Material Design principles](https://material.io/guidelines/). 8 | 9 | Skawa Material Components goal is to provide a couple of components that seems to be missing from `package:angular_components`. 10 | Skawa Components goal is to share a couple of components whiches are used internally but not material. 11 | 12 | Both packages are being actively maintained, meaning components get added and removed. 13 | In case of a component from this list is added to angular_components, it will be 14 | deprecated and eventually removed from skawa_material_components. 15 | 16 | 17 | Skawa Material Components and Skawa Components reuses and builds on top of certain parts of `angular_components` package. 18 | (Namely, base SCSS files) [angular_components license](https://github.com/dart-lang/angular_components/blob/master/LICENSE) 19 | 20 | ## Getting started 21 | 22 | See [examples](https://github.com/skawa-universe/skawa_components_example) -------------------------------------------------------------------------------- /skawa_material_components/test/skawa_banner/skawa_banner_host/skawa_banner_host.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | import 'package:angular_components/material_button/material_button.dart'; 3 | import 'package:skawa_material_components/skawa_banner/skawa_banner.dart'; 4 | 5 | @Component( 6 | selector: 'skawa-banner-host', 7 | templateUrl: 'skawa_banner_host.html', 8 | styleUrls: ['skawa_banner_host.css'], 9 | directives: [SkawaBannerComponent, MaterialButtonComponent]) 10 | class SkawaBannerHostComponent { 11 | final SkawaBannerService _bannerService; 12 | 13 | SkawaBannerMessage warningMessage; 14 | SkawaBannerMessage infoMessage; 15 | SkawaBannerMessage errorMessage; 16 | 17 | SkawaBannerHostComponent(this._bannerService); 18 | 19 | void showWarning() { 20 | warningMessage = SkawaBannerMessage.warning("This is a warning"); 21 | _bannerService.showMessage(warningMessage); 22 | } 23 | 24 | void showInfo(bool priority) { 25 | infoMessage = 26 | infoMessage = SkawaBannerMessage.info("This is an info", beforeDispatch: priority ? null : () => false); 27 | _bannerService.showMessage(infoMessage); 28 | } 29 | 30 | void showError() { 31 | errorMessage = SkawaBannerMessage.error("This is an error"); 32 | _bannerService.showMessage(errorMessage); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /skawa_components/lib/master_detail/master_detail.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/core.dart'; 2 | 3 | import '../util/attribute.dart' as attr_util; 4 | 5 | /// Master-detail component. 6 | /// 7 | /// __Example__: 8 | /// 9 | /// 10 | /// 11 | /// Hello 12 | /// 13 | /// 14 | /// world 15 | /// 16 | /// 17 | /// 18 | /// __Properties__: 19 | /// 20 | /// - `expanded: bool` -- Whether details should be shown. 21 | /// 22 | @Component( 23 | selector: 'skawa-master-detail', 24 | templateUrl: 'master_detail.html', 25 | styleUrls: ['master_detail.css'], 26 | changeDetection: ChangeDetectionStrategy.OnPush) 27 | class SkawaMasterDetailComponent { 28 | SkawaMasterDetailComponent(@Optional() @Attribute('expanded') String expanded) 29 | : expanded = attr_util.isPresent(expanded); 30 | 31 | /// Expands/collapses the details view 32 | /// 33 | /// Instead of setting this property one can use [expand] 34 | /// and [collapse] methods. 35 | bool expanded; 36 | 37 | @HostBinding('attr.expanded') 38 | String get isExpanded => expanded ? "" : null; 39 | 40 | void expand() => expanded = true; 41 | 42 | void collapse() => expanded = false; 43 | 44 | void toggle() => expanded = !expanded; 45 | } 46 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | - .dart_tool/** 4 | - build/** 5 | - test/**.g.dart 6 | strong-mode: 7 | implicit-casts: false 8 | plugins: 9 | - angular 10 | errors: 11 | unused_element: error 12 | unused_import: error 13 | unused_local_variable: error 14 | dead_code: error 15 | 16 | # Allow importing .template.dart files without an analyzer error. 17 | uri_has_not_been_generated: ignore 18 | 19 | linter: 20 | rules: 21 | - avoid_empty_else 22 | - avoid_init_to_null 23 | - avoid_null_checks_in_equality_operators 24 | - await_only_futures 25 | - camel_case_types 26 | - cancel_subscriptions 27 | - control_flow_in_finally 28 | - directives_ordering 29 | - empty_catches 30 | - empty_constructor_bodies 31 | - empty_statements 32 | - hash_and_equals 33 | - iterable_contains_unrelated_type 34 | - library_names 35 | - library_prefixes 36 | - list_remove_unrelated_type 37 | - only_throw_errors 38 | - overridden_fields 39 | - package_api_docs 40 | - package_names 41 | - package_prefixed_library_names 42 | - prefer_final_fields 43 | - prefer_is_not_empty 44 | - slash_for_doc_comments 45 | - test_types_in_equals 46 | - throw_in_finally 47 | - type_init_formals 48 | - unnecessary_brace_in_string_interps 49 | - unrelated_type_equality_checks 50 | - valid_regexps -------------------------------------------------------------------------------- /skawa_components/lib/pipes/hex_colorize_pipe.dart: -------------------------------------------------------------------------------- 1 | import "package:angular/di.dart" show PipeTransform, Pipe; 2 | import 'package:angular/src/common/pipes/invalid_pipe_argument_exception.dart'; 3 | 4 | /// Based on [seed] provided to the pipe, return a css consumable color 5 | /// 6 | /// Returned format: `#somecolor` 7 | /// 8 | /// __Usage__: 9 | /// 10 | ///
11 | /// 12 | /// __Limitations__: 13 | /// 14 | /// Can only accept seeds of `String` and `int` types also support hexadecimal numbers 15 | @Pipe('hexColorize') 16 | class SkawaHexColorizePipe implements PipeTransform { 17 | String transform(dynamic seed) { 18 | // print('seed: $seed'); 19 | if (!_supportedInput(seed)) { 20 | throw InvalidPipeArgumentException(SkawaHexColorizePipe, seed); 21 | } 22 | int hexHash; 23 | if (seed is String) { 24 | hexHash = int.tryParse(seed ?? '', radix: 16) ?? (seed ?? '').hashCode; 25 | } else { 26 | hexHash = seed.hashCode; 27 | } 28 | String hash = hexHash.toRadixString(16); 29 | while (hash.length < validHashLength.first) { 30 | hash = '${hash}0'; 31 | } 32 | while (!validHashLength.contains(hash.length)) { 33 | hash = hash.substring(1); 34 | } 35 | return '#$hash'; 36 | } 37 | 38 | static const validHashLength = const [3, 4, 6, 8]; 39 | 40 | bool _supportedInput(dynamic input) => input == null || input is String || input is num; 41 | } 42 | -------------------------------------------------------------------------------- /skawa_material_components/test/skawa_banner/skawa_banner_po.dart: -------------------------------------------------------------------------------- 1 | import 'package:pageloader/pageloader.dart'; 2 | 3 | part 'skawa_banner_po.g.dart'; 4 | 5 | @PageObject() 6 | @CheckTag('skawa-banner') 7 | abstract class SkawaBannerPO { 8 | SkawaBannerPO(); 9 | 10 | factory SkawaBannerPO.create(PageLoaderElement context) = $SkawaBannerPO.create; 11 | 12 | @root 13 | PageLoaderElement get rootElement; 14 | 15 | @ByCss('section.banner') 16 | PageLoaderElement get section; 17 | 18 | @ByCss('div.message span') 19 | PageLoaderElement get messageText; 20 | 21 | @ByCss('div.message material-icon') 22 | PageLoaderElement get messageIcon; 23 | 24 | @ByCss('material-button') 25 | List get actions; 26 | 27 | PageLoaderElement get iconI => 28 | messageIcon?.getElementsByCss("i") != null && messageIcon.getElementsByCss("i").isNotEmpty 29 | ? messageIcon.getElementsByCss("i").first 30 | : null; 31 | 32 | String get iconName => iconI?.innerText; 33 | 34 | String get iconClass => messageIcon.exists 35 | ? messageIcon.classes.firstWhere((String className) => className != "icon-large", orElse: () => null) 36 | : null; 37 | 38 | String get message => messageText.exists ? messageText.visibleText : null; 39 | 40 | Future trigger(int i) async { 41 | if (actions == null || actions.length <= i) { 42 | throw StateError("No such action $i in $actions"); 43 | } 44 | await actions.elementAt(i).click(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /skawa_components/README.md: -------------------------------------------------------------------------------- 1 | # skawa_components 2 | 3 | [![Pub Package](https://img.shields.io/pub/v/skawa_components.svg)](https://pub.dev/packages/skawa_components) 4 | 5 | ## "Non-Material" Components 6 | 7 | These components are following the Material Design principles, but not part of the [material components](https://material.io/guidelines/components/). 8 | 9 | The goal is to share a couple of components which are used internally but not material. 10 | 11 | **Currently Available components:** 12 | 13 | * ✓ `` 14 | * ✓ `` 15 | * ✓ `` 16 | * ✓ `` 17 | * ✓ `` 18 | * ✓ `` 19 | 20 | **Currently Available directives**: 21 | 22 | * ✓ `*featureEnabled` and `*featureDisabled` 23 | 24 | **Currently Available pipes:** 25 | 26 | * ✓ `hexColorize` 27 | 28 | ## Getting started 29 | 30 | See [examples](https://github.com/skawa-universe/skawa_components_example) 31 | 32 | ## Need a component? 33 | 34 | Let us know what you'd like to use, open an issue! 35 | 36 | ## Looking for maintainers 37 | 38 | If you have a component you'd like to be part of this collection, let us know, we can chat! 39 | 40 | ### Licensing 41 | 42 | Skawa Components reuses and builds on top of certain parts of `angular_components` package. (Namely, base SCSS files) 43 | [angular_components license](https://github.com/dart-lang/angular_components/blob/master/LICENSE) 44 | 45 | [Skawa Components](https://github.com/skawa-universe/skawa_components/tree/master/skawa_components) 46 | is released under MIT license. -------------------------------------------------------------------------------- /skawa_components/lib/sidebar_item/sidebar_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | import 'package:angular_components/material_icon/material_icon.dart'; 3 | import '../util/attribute.dart' as attrib; 4 | 5 | /// Toolbar item. See more [about normal lists](https://material.io/guidelines/components/lists.html#) or 6 | /// [about control lists](https://material.io/guidelines/components/lists-controls.html) 7 | /// *Note:* Sidebar is not yet fully implemented. Currently only support use within a Drawer. Please see [#123](plans for sidebar) 8 | /// 9 | /// __Example usage:__ 10 | /// No icon, with icon placeholder 11 | /// Will have icon 12 | /// No icon, without icon placeholder 13 | /// 14 | /// __Properties:__ 15 | /// 16 | /// - `icon` -- Optional icon to display next to display text, leaving it's place as placeholder. 17 | /// 18 | /// __Attributes:__ 19 | /// 20 | /// - `textOnly` -- If present, `icon` will be ignored and it's place removed. 21 | /// 22 | @Component( 23 | selector: 'skawa-sidebar-item', 24 | templateUrl: 'sidebar_item.html', 25 | styleUrls: ['sidebar_item.css'], 26 | directives: [MaterialIconComponent, NgIf], 27 | changeDetection: ChangeDetectionStrategy.OnPush) 28 | class SkawaSidebarItemComponent extends Object with TextOnlyMixin { 29 | @Input() 30 | String icon; 31 | } 32 | 33 | abstract class TextOnlyMixin { 34 | @HostBinding('attr.textOnly') 35 | @Input() 36 | String textOnly; 37 | 38 | bool get isTextOnly => attrib.isPresent(textOnly); 39 | } 40 | -------------------------------------------------------------------------------- /skawa_material_components/lib/card/card_header.scss: -------------------------------------------------------------------------------- 1 | @import "package:skawa_material_components/css/skawa-material"; 2 | 3 | :host { 4 | padding: 16px; 5 | display: flex; 6 | align-items: center; 7 | &::after { 8 | content: ''; 9 | display: block; 10 | clear: both; 11 | } 12 | ::ng-deep { 13 | skawa-header-title, skawa-header-subhead { 14 | display: block; 15 | } 16 | skawa-header-image { 17 | display: inline-block; 18 | float: left; 19 | box-sizing: border-box; 20 | width: 40px; 21 | height: 40px; 22 | margin-right: 8px; 23 | } 24 | skawa-header-title { 25 | @include typo-headline; 26 | } 27 | skawa-header-subhead { 28 | font-size: $mat-card-subhead-font-size; 29 | color: $mat-light-transparent-black; 30 | } 31 | } 32 | &.with-title-image { 33 | ::ng-deep { 34 | skawa-header-subhead { 35 | font-size: $mat-card-subhead-font-size; 36 | color: $mat-light-transparent-black; 37 | } 38 | } 39 | &.with-subhead { 40 | display: block; 41 | ::ng-deep { 42 | skawa-header-title { 43 | @include typo-title--dense; 44 | padding-top: 0; 45 | font-weight: 700; 46 | } 47 | } 48 | } 49 | } 50 | &.with-actions { 51 | display: flex; 52 | padding-right: 8px; 53 | ::ng-deep { 54 | skawa-header-title { 55 | flex-grow: 1; 56 | } 57 | } 58 | } 59 | &.with-subhead { 60 | flex-direction: column; 61 | align-items: flex-start; 62 | } 63 | } 64 | 65 | :host-context(.with-data-table) { 66 | padding: 12px 6px 12px 16px; 67 | } 68 | -------------------------------------------------------------------------------- /skawa_material_components/README.md: -------------------------------------------------------------------------------- 1 | # skawa_material_components 2 | 3 | [![Pub Package](https://img.shields.io/pub/v/skawa_material_components.svg)](https://pub.dev/packages/skawa_material_components) 4 | 5 | ## More "Material" Components 6 | 7 | These components are based on Material Design and built on top of `package:angular_components`. 8 | 9 | The goal is to provide a couple of components that seem to be missing from angular_components. 10 | 11 | **Currently Available components:** 12 | 13 | * ✓ `` 14 | * ✓ `` 15 | * ✓ `` 16 | * ✓ `` 17 | 18 | Both packages are being actively maintained, meaning components get added and removed. 19 | In case of a component from this list is added to angular_components, it will be 20 | deprecated and eventually removed from skawa_material_components. 21 | 22 | **Removed components:** 23 | 24 | * ✓ `` 25 | * ✓ `` 26 | * ✓ `` 27 | 28 | 29 | ## Getting started 30 | 31 | See [examples](https://github.com/skawa-universe/skawa_components_example) 32 | 33 | ## Need a component? 34 | 35 | Let us know what you'd like to use, open an issue! 36 | 37 | ## Looking for maintainers 38 | 39 | If you have a component you'd like to be part of this collection, let us know, we can chat! 40 | 41 | ### Licensing 42 | 43 | Skawa Material Components reuses and builds on top of certain parts of `angular_components` package. (Namely, base SCSS files) 44 | [angular_components license](https://github.com/dart-lang/angular_components/blob/master/LICENSE) 45 | 46 | [Skawa Material Components](https://github.com/skawa-universe/skawa_components/tree/master/skawa_material_components) 47 | is released under MIT license. -------------------------------------------------------------------------------- /skawa_material_components/lib/css/_responsiveness.scss: -------------------------------------------------------------------------------- 1 | $small-mobile: 320px !default; 2 | $mobile: 480px !default; 3 | $tablet: 768px !default; 4 | $desktop: 1000px !default; 5 | $widescreen: 1192px !default; 6 | $fullhd: 1384px !default; 7 | 8 | @mixin from($device) { 9 | @media screen and (min-width: $device) { 10 | @content; 11 | } 12 | } 13 | 14 | @mixin until($device) { 15 | @media screen and (max-width: $device - 1px) { 16 | @content; 17 | } 18 | } 19 | 20 | @mixin small-mobile { 21 | @media screen and (max-width: $small-mobile - 1px) { 22 | @content; 23 | } 24 | } 25 | 26 | @mixin mobile { 27 | @media screen and (max-width: $tablet - 1px) { 28 | @content; 29 | } 30 | } 31 | 32 | @mixin tablet { 33 | @media screen and (min-width: $tablet) { 34 | @content; 35 | } 36 | } 37 | 38 | @mixin tablet-only { 39 | @media screen and (min-width: $tablet) and (max-width: $desktop - 1px) { 40 | @content; 41 | } 42 | } 43 | 44 | @mixin touch { 45 | @media screen and (max-width: $desktop - 1px) { 46 | @content; 47 | } 48 | } 49 | 50 | @mixin desktop { 51 | @media screen and (min-width: $desktop) { 52 | @content; 53 | } 54 | } 55 | 56 | @mixin desktop-only { 57 | @media screen and (min-width: $desktop) and (max-width: $widescreen - 1px) { 58 | @content; 59 | } 60 | } 61 | 62 | @mixin widescreen { 63 | @media screen and (min-width: $widescreen) { 64 | @content; 65 | } 66 | } 67 | 68 | @mixin widescreen-only { 69 | @media screen and (min-width: $widescreen) and (max-width: $fullhd - 1px) { 70 | @content; 71 | } 72 | } 73 | 74 | @mixin fullhd { 75 | @media screen and (min-width: $fullhd) { 76 | @content; 77 | } 78 | } -------------------------------------------------------------------------------- /skawa_material_components/test/card_po.dart: -------------------------------------------------------------------------------- 1 | import 'package:pageloader/html.dart'; 2 | import 'package:pageloader/webdriver.dart'; 3 | 4 | part 'card_po.g.dart'; 5 | 6 | @PageObject() 7 | @CheckTag('test') 8 | abstract class TestPO { 9 | @ByTagName('skawa-card') 10 | CardPO get card; 11 | 12 | TestPO(); 13 | 14 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 15 | } 16 | 17 | @PageObject() 18 | abstract class CardPO { 19 | PageLoaderElement context; 20 | 21 | CardPO(); 22 | 23 | factory CardPO.create(PageLoaderElement context) = $CardPO.create; 24 | 25 | @ByTagName('skawa-card-header') 26 | HeaderPO get header; 27 | 28 | @ByCss('.actions') 29 | ActionPO get actions; 30 | 31 | @ByTagName('skawa-card-content') 32 | ContentPO get content; 33 | } 34 | 35 | @PageObject() 36 | abstract class ContentPO { 37 | ContentPO(); 38 | 39 | factory ContentPO.create(PageLoaderElement context) = $ContentPO.create; 40 | 41 | @root 42 | PageLoaderElement get rootElement; 43 | } 44 | 45 | @PageObject() 46 | abstract class ActionPO { 47 | ActionPO(); 48 | 49 | factory ActionPO.create(PageLoaderElement context) = $ActionPO.create; 50 | 51 | @root 52 | PageLoaderElement get rootElement; 53 | } 54 | 55 | @PageObject() 56 | abstract class HeaderPO { 57 | HeaderPO(); 58 | 59 | factory HeaderPO.create(PageLoaderElement context) = $HeaderPO.create; 60 | 61 | @root 62 | PageLoaderElement get rootElement; 63 | 64 | @ByTagName('skawa-header-image') 65 | PageLoaderElement get image; 66 | 67 | @ByTagName('skawa-header-title') 68 | PageLoaderElement get title; 69 | 70 | @ByTagName('skawa-header-subhead') 71 | PageLoaderElement get subhead; 72 | 73 | @ByTagName('skawa-card-actions') 74 | PageLoaderElement get actions; 75 | } 76 | -------------------------------------------------------------------------------- /skawa_material_components/lib/skawa_banner/src/skawa_banner.dart: -------------------------------------------------------------------------------- 1 | part of skawa_banner; 2 | 3 | @Component( 4 | selector: 'skawa-banner', 5 | templateUrl: 'src/skawa_banner.html', 6 | styleUrls: ['src/skawa_banner.css'], 7 | directives: [MaterialIconComponent, MaterialButtonComponent, coreDirectives], 8 | changeDetection: ChangeDetectionStrategy.OnPush) 9 | class SkawaBannerComponent implements OnInit, OnDestroy { 10 | final ChangeDetectorRef _changeDetectorRef; 11 | final SkawaBannerService _bannerService; 12 | final Disposer disposer = Disposer.oneShot(); 13 | 14 | bool active = false; 15 | bool show = false; 16 | SkawaBannerMessage message; 17 | 18 | SkawaBannerComponent(this._changeDetectorRef, this._bannerService); 19 | 20 | bool get hasActions => message?.actions != null && message.actions.isNotEmpty; 21 | 22 | void animationEnd() { 23 | if (!show) { 24 | message.dismiss(); 25 | message = null; 26 | active = false; 27 | } 28 | } 29 | 30 | Future handleAction(BannerAction action) async { 31 | bool canDismiss = true; 32 | if (action.callback != null) { 33 | canDismiss = await action.callback(); 34 | } 35 | if (canDismiss) { 36 | show = false; 37 | _changeDetectorRef.markForCheck(); 38 | } 39 | } 40 | 41 | @override 42 | Future ngOnInit() async { 43 | disposer.addStreamSubscription(_bannerService.dispatch.listen((SkawaBannerMessage event) async { 44 | message = null; 45 | show = false; 46 | active = true; 47 | _changeDetectorRef.markForCheck(); 48 | await window.animationFrame; 49 | message = event; 50 | show = true; 51 | _changeDetectorRef.markForCheck(); 52 | })); 53 | } 54 | 55 | @override 56 | void ngOnDestroy() => disposer.dispose(); 57 | } 58 | -------------------------------------------------------------------------------- /skawa_material_components/test/grid_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:angular/angular.dart'; 3 | import 'package:angular_test/angular_test.dart'; 4 | import 'package:pageloader/html.dart'; 5 | import 'package:pageloader/webdriver.dart'; 6 | import 'package:skawa_material_components/grid/grid_component.dart'; 7 | import 'package:test/test.dart'; 8 | import 'grid_test.template.dart' as ng; 9 | 10 | part 'grid_test.g.dart'; 11 | 12 | void main() { 13 | ng.initReflector(); 14 | tearDown(disposeAnyRunningTest); 15 | group('Grid | ', () { 16 | test('initialization with 3 grid', () async { 17 | final fixture = await NgTestBed().create(); 18 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 19 | final pageObject = TestPO.create(context); 20 | expect(pageObject.grid, isNotNull); 21 | var tiles = pageObject.grid.tiles; 22 | expect(tiles.length, 3); 23 | tiles.forEach((PageLoaderElement tile) => expect(tile.getBoundingClientRect().width.round(), 280)); 24 | }); 25 | }); 26 | } 27 | 28 | @Component(selector: 'test', template: ''' 29 | 30 |
Cat
31 |
Dog
32 |
Wolf
33 |
34 | ''', directives: [GridComponent, GridTileDirective]) 35 | class GridTestComponent {} 36 | 37 | @PageObject() 38 | @CheckTag('test') 39 | abstract class TestPO { 40 | TestPO(); 41 | 42 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 43 | 44 | @ByTagName('skawa-grid') 45 | GridPO get grid; 46 | } 47 | 48 | @PageObject() 49 | abstract class GridPO { 50 | GridPO(); 51 | 52 | factory GridPO.create(PageLoaderElement context) = $GridPO.create; 53 | 54 | @root 55 | PageLoaderElement get rootElement; 56 | 57 | @ByCss('[gridTile]') 58 | List get tiles; 59 | } 60 | -------------------------------------------------------------------------------- /skawa_components/lib/code_mirror/code_mirror_script_loader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:skawa_components/code_mirror/base_loader.dart'; 3 | 4 | import 'code_mirror_mode_with_link.dart'; 5 | 6 | class CodeMirrorScriptLoader { 7 | final BaseLoader _baseLoader = BaseLoader(); 8 | 9 | Future load(CodeMirrorModeWithLink mode) async { 10 | await _baseLoader.loadList([ScriptLoader(_codeMirrorCdnLink), LinkLoader(_codeMirrorCssCdnLink)]); 11 | await _baseLoader.loadList([ 12 | ...[...mode.linterScriptList].map((String script) => ScriptLoader(script)), 13 | ScriptLoader(_showHintJsCdnLink), 14 | ScriptLoader(_lintJsCdnLink), 15 | ...[...mode.scriptList].map((String script) => ScriptLoader(script)), 16 | LinkLoader(_themeCdnLink.replaceAll(_replaceString, mode.theme)), 17 | LinkLoader(_lintCssCdnLink), 18 | LinkLoader(_showHintCssCdnLink) 19 | ]); 20 | } 21 | 22 | static const String _codeMirrorCdnLink = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.43.0/codemirror.js'; 23 | static const String _codeMirrorCssCdnLink = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.43.0/codemirror.css'; 24 | static const String _themeCdnLink = 25 | 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.43.0/theme/$_replaceString.css'; 26 | static const String _showHintJsCdnLink = 27 | 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.43.0/addon/hint/show-hint.js'; 28 | static const String _showHintCssCdnLink = 29 | 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.43.0/addon/hint/show-hint.css'; 30 | static const String _lintJsCdnLink = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.43.0/addon/lint/lint.js'; 31 | static const String _lintCssCdnLink = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.43.0/addon/lint/lint.css'; 32 | static const String _replaceString = 'REPLACE'; 33 | } 34 | -------------------------------------------------------------------------------- /skawa_material_components/lib/skawa_banner/src/skawa_banner_service.dart: -------------------------------------------------------------------------------- 1 | part of skawa_banner; 2 | 3 | class SkawaBannerService { 4 | /// The message currently displayed by the banner component 5 | SkawaBannerMessage current; 6 | 7 | /// Messages waiting in the queue for later appearance 8 | final List messageQueue = []; 9 | 10 | final StreamController _dispatchController = StreamController.broadcast(); 11 | 12 | Stream get dispatch => _dispatchController.stream; 13 | 14 | /// Either displays the message or puts it in the queue, based on message priority and already displayed message 15 | Future showMessage(SkawaBannerMessage message) { 16 | if (message.priority == BannerMessagePriority.normal || current == null) { 17 | // Dispatch immediately 18 | _dispatch(message); 19 | } else { 20 | messageQueue.add(message); 21 | } 22 | message.dismissEvent.future.then((_) => _dispatchNext()); 23 | return message.dispatchEvent.future; 24 | } 25 | 26 | void _dispatchNext() { 27 | current = null; 28 | SkawaBannerMessage nextMessage; 29 | while (nextMessage == null && messageQueue.isNotEmpty) { 30 | nextMessage = messageQueue.removeAt(0); 31 | // Check if this message is still relevant (i.e. its TTL value is not yet expired) 32 | if (nextMessage.ttlInQueue != null && 33 | (DateTime.now().millisecondsSinceEpoch - nextMessage.timeCreated.millisecondsSinceEpoch > 34 | nextMessage.ttlInQueue)) { 35 | nextMessage = null; 36 | } else if (nextMessage.beforeDispatch != null) { 37 | if (!nextMessage.beforeDispatch()) { 38 | nextMessage = null; 39 | } 40 | } 41 | } 42 | if (nextMessage != null) { 43 | _dispatch(nextMessage); 44 | } 45 | } 46 | 47 | void _dispatch(SkawaBannerMessage message) { 48 | current = message; 49 | current.dispatch(); 50 | _dispatchController.add(current); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /skawa_components/test/hex_colorize_pipe_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/src/common/pipes/invalid_pipe_argument_exception.dart'; 2 | import 'package:skawa_components/pipes/hex_colorize_pipe.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | final Matcher throwsATypeError = throwsA(TypeMatcher()); 6 | 7 | final Matcher throwsAnInvalidPipeArgumentException = throwsA(TypeMatcher()); 8 | 9 | void main() { 10 | group('HexColorizePipe | ', () { 11 | SkawaHexColorizePipe pipe; 12 | final int intSeed = 1234; 13 | final String stringSeed = 'seed'; 14 | final String hexSeed = '2cbe4e'; 15 | setUp(() => pipe = SkawaHexColorizePipe()); 16 | test('accepts int', () { 17 | expect(() { 18 | pipe.transform(intSeed); 19 | }, returnsNormally); 20 | }); 21 | test('accepts String', () { 22 | expect(() => pipe.transform(stringSeed), returnsNormally); 23 | }); 24 | test('throws for types not int or String', () { 25 | expect(() => pipe.transform(Object()), throwsAnInvalidPipeArgumentException); 26 | }); 27 | test('returns same color for the same int seed', () { 28 | String a = pipe.transform(intSeed); 29 | String b = pipe.transform(intSeed); 30 | expect(a, allOf(const TypeMatcher(), b)); 31 | }); 32 | test('returns valid css color', () { 33 | String a = pipe.transform(intSeed); 34 | expect(a, startsWith('#')); 35 | expect(SkawaHexColorizePipe.validHashLength.contains(a.length - 1), isTrue); 36 | }); 37 | test('returns same color for the same string seed', () { 38 | String a = pipe.transform(stringSeed); 39 | String b = pipe.transform(stringSeed); 40 | expect(a, b); 41 | }); 42 | test('keeps hex numbers', () { 43 | String b = pipe.transform(hexSeed); 44 | expect(b, contains(hexSeed)); 45 | }); 46 | test("output can't be 6 length", () { 47 | String b = pipe.transform(hexSeed.substring(1)); 48 | expect(b, hasLength(5)); 49 | }); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /skawa_material_components/lib/extended_material_icon/extended_material_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | 3 | /// A material styled icon component. On the top of the official icons it is containing 4 | /// at least 3000 icons from the community. Currently this component supports only the dead-weight way. 5 | /// 6 | /// This stylesheet should be included at the top of the page: 7 | /// 8 | /// ```html 9 | /// 13 | /// ``` 14 | /// 15 | /// __Attributes:__ 16 | /// 17 | /// - `size: String` -- The size of the icon. Options are: `x-small`, `small`, 18 | /// `medium`, `large`, and `x-large`, corresponding to 12px, 13px, 16px, 18px, 19 | /// and 20px, respectively. If no size is specified, the default of 24px is 20 | /// used. 21 | /// - `flipVertical` -- Whether the icon should be flipped vertically. 22 | /// - `flipHorizontal` -- Whether the icon should be flipped horizontally. 23 | /// - `rotate` -- With how many degree should the icon rotated. Usable only with multiple of 45. 24 | /// Works only if flipVertical and flipHorizontal false. 25 | /// - `spin` -- Should the icon spinning. 26 | /// 27 | @Component( 28 | selector: 'extended-material-icon', 29 | templateUrl: 'extended_material_icon.html', 30 | styleUrls: ['extended_material_icon.css'], 31 | directives: [NgClass], 32 | changeDetection: ChangeDetectionStrategy.OnPush) 33 | class ExtendedMaterialIconComponent { 34 | @Input() 35 | String icon; 36 | 37 | @Input() 38 | bool flipVertical = false; 39 | 40 | @Input() 41 | bool flipHorizontal = false; 42 | 43 | @Input() 44 | bool spin = false; 45 | 46 | @Input() 47 | int rotate; 48 | 49 | Map get extraClasses => { 50 | "mdi-flip-h": flipHorizontal, 51 | "mdi-flip-v": flipVertical, 52 | "mdi-spin": spin, 53 | 'mdi-rotate-${(rotate ?? 0) % 360}': !flipHorizontal && !flipVertical && rotate != null && rotate % 45 == 0 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /skawa_components/lib/infobar/infobar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:html'; 3 | import 'package:angular/angular.dart'; 4 | import 'package:angular_components/material_icon/material_icon.dart'; 5 | import 'package:angular_components/material_button/material_button.dart'; 6 | 7 | /// An Inforbar is compositing an (icon as button)[https://material.io/components/web/catalog/buttons/icon-toggle-buttons/] and 8 | /// an arbitrary component. Infobar is designed to display small notifications, important messages to the user, *in-context*. 9 | /// 10 | /// This differs from "snackbars" and "toasts" that serve as feedback of actions. 11 | /// 12 | /// *Note:* Infobar component uses Material Icon font, make sure it is available 13 | /// 14 | /// __Example usage:__ 15 | /// 16 | /// 17 | /// 18 | /// __Inputs__: 19 | /// 20 | /// - `icon`: Icon to display in the infobar. 21 | /// - `url`: Url to navigate the user to when icon is triggered. 22 | /// 23 | /// __Events:__ 24 | /// 25 | /// - `trigger: Event` -- Published when the `icon` is triggered. 26 | /// 27 | @Component( 28 | selector: 'skawa-infobar', 29 | templateUrl: 'infobar.html', 30 | exportAs: 'infobar', 31 | styleUrls: ['infobar.css'], 32 | directives: [MaterialIconComponent, MaterialButtonComponent], 33 | changeDetection: ChangeDetectionStrategy.OnPush) 34 | class SkawaInfobarComponent { 35 | final ChangeDetectorRef _changeDetectorRef; 36 | 37 | String _icon; 38 | 39 | SkawaInfobarComponent(this._changeDetectorRef); 40 | 41 | @Input() 42 | set icon(String icon) { 43 | _icon = icon; 44 | _changeDetectorRef.markForCheck(); 45 | } 46 | 47 | String get icon { 48 | if (_icon != null) return _icon; 49 | if (url != null) return 'info'; 50 | return null; 51 | } 52 | 53 | @Input() 54 | String url; 55 | 56 | @ViewChild(MaterialButtonComponent) 57 | MaterialButtonComponent primaryActionButton; 58 | 59 | @Output('trigger') 60 | Stream get onTrigger => primaryActionButton.trigger; 61 | 62 | void navigate() { 63 | if (url != null) window.open(url, '_blank'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /skawa_components/lib/markdown_editor/editor_render_target.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:html'; 3 | import 'package:angular/core.dart'; 4 | import 'package:markdown/markdown.dart' as markdown; 5 | 6 | /// Target where rendered output of EditorRendererSource will be inserted 7 | /// 8 | /// __Providers__: 9 | /// 10 | /// - `EditorRenderer` -- renderer to use to convert EditorRendererSource.value to a DOM fragment 11 | @Directive(selector: '[editorRenderTarget]', exportAs: 'editorRenderTarget') 12 | class EditorRenderTarget implements OnDestroy { 13 | final HtmlElement htmlElement; 14 | final EditorRenderer renderer; 15 | final StreamController _onRenderController = StreamController.broadcast(); 16 | 17 | String _previousRender; 18 | 19 | EditorRenderTarget(this.htmlElement, @SkipSelf() @Inject(EditorRenderer) this.renderer); 20 | 21 | @Output('render') 22 | Stream get onRender => _onRenderController.stream; 23 | 24 | Element get _element => htmlElement; 25 | 26 | void updateRender(String newTarget, {List classes}) { 27 | _onRenderController.add(newTarget); 28 | if (newTarget == _previousRender) return; 29 | _element.children.clear(); 30 | _element.append(renderer.render(newTarget)); 31 | _updateElementChildren(_element, classes); 32 | } 33 | 34 | void _updateElementChildren(Element element, List classes) { 35 | if (classes != null && classes.isNotEmpty && element.children.isNotEmpty) { 36 | element.children.forEach((Element child) { 37 | child.classes.addAll(classes); 38 | _updateElementChildren(child, classes); 39 | }); 40 | } 41 | } 42 | 43 | @override 44 | void ngOnDestroy() => _onRenderController.close(); 45 | } 46 | 47 | /// Interface to describe a renderer 48 | /// 49 | /// Renderers are responsible for converting an arbitrary source 50 | /// piece to a format directly usable by DOM 51 | abstract class EditorRenderer { 52 | /// Converts [source] to a [DocumentFragment] 53 | DocumentFragment render(String source); 54 | } 55 | 56 | /// Renders HTML source 57 | class HtmlRenderer implements EditorRenderer { 58 | @override 59 | DocumentFragment render(String source) => DocumentFragment.html(source); 60 | } 61 | 62 | /// Renders Markdown source 63 | class MarkdownRenderer implements EditorRenderer { 64 | @override 65 | DocumentFragment render(String source) => DocumentFragment.html(markdown.markdownToHtml(source)); 66 | } 67 | -------------------------------------------------------------------------------- /skawa_components/lib/list_wrapper/list_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:html'; 3 | 4 | import 'package:angular/core.dart'; 5 | 6 | /// This component combined with the `SkawaForDirective` can be used to efficiently display lists of arbitrary length. 7 | /// The component and the directive together work by only leaving the currently visible list items in the DOM, 8 | /// and replacing the out-of-view items with two empty div elements (one at the top and one the bottom). 9 | /// This will result in a smooth list rendering and scrolling experience. 10 | /// One caveat is that the browser "Find" command will not be able to search through all list items, as the non-visible 11 | /// ones are physically removed from the DOM. 12 | /// 13 | /// 14 | /// __Example usage:__ 15 | /// 16 | /// {{item}} 17 | /// 18 | /// 19 | /// 20 | /// __Events:__ 21 | /// 22 | /// - `reachBottom: Event` -- Published when the list is scrolled down to the bottom. 23 | /// It can be used to trigger loading further elements from a service. 24 | /// 25 | 26 | @Component( 27 | selector: 'list-wrapper', 28 | templateUrl: 'list_wrapper.html', 29 | styleUrls: ['list_wrapper.css'], 30 | changeDetection: ChangeDetectionStrategy.OnPush) 31 | class SkawaListWrapperComponent implements OnDestroy { 32 | final StreamController _reachBottomController = StreamController(); 33 | final StreamController _reachTopController = StreamController(); 34 | final ChangeDetectorRef changeDetectorRef; 35 | final HtmlElement htmlElement; 36 | 37 | @ViewChild('topPlaceHolder') 38 | HtmlElement topPlaceHolder; 39 | 40 | @ViewChild('bottomPlaceholder') 41 | HtmlElement bottomPlaceholder; 42 | 43 | SkawaListWrapperComponent(this.changeDetectorRef, this.htmlElement); 44 | 45 | @Output('reachBottom') 46 | Stream get onReachBottom => _reachBottomController.stream; 47 | 48 | void setUpHeights(int topHeight, int bottomHeight, bool shouldTriggerBottomReach) { 49 | bottomPlaceholder.style.height = "${bottomHeight}px"; 50 | topPlaceHolder.style.height = "${topHeight}px"; 51 | if (bottomHeight == 0 && shouldTriggerBottomReach) _reachBottomController.add(null); 52 | if (topHeight == 0) _reachTopController.add(null); 53 | } 54 | 55 | @override 56 | void ngOnDestroy() { 57 | _reachBottomController.close(); 58 | _reachTopController.close(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /skawa_material_components/lib/data_table/sort.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart'; 2 | import 'data_table_column.dart'; 3 | 4 | enum SortDirection { asc, desc } 5 | 6 | class SortModel { 7 | final List allowedDirections; 8 | 9 | SortDirection activeSort; 10 | 11 | SortModel(this.allowedDirections); 12 | 13 | void toggleSort() { 14 | if (((allowedDirections ?? const [])).isEmpty) { 15 | throw ArgumentError('SortModel does not have any allowed sort directions'); 16 | } 17 | if (activeSort == null) { 18 | activeSort = allowedDirections.first; 19 | } else { 20 | int directionIndex = allowedDirections.indexOf(activeSort); 21 | if (directionIndex == allowedDirections.length - 1) { 22 | activeSort = null; 23 | } else { 24 | activeSort = allowedDirections[directionIndex + 1]; 25 | } 26 | } 27 | } 28 | 29 | bool get isAscending => activeSort == SortDirection.asc; 30 | 31 | bool get isDescending => activeSort == SortDirection.desc; 32 | 33 | bool get isSorted => activeSort != null; 34 | } 35 | 36 | @Directive(selector: '[sortable]') 37 | class SkawaDataTableSortDirective { 38 | final SkawaDataTableColComponent column; 39 | 40 | SkawaDataTableSortDirective(this.column); 41 | 42 | @Input('sortable') 43 | set allowedSorts(String allowedSorts) { 44 | List directions; 45 | if ((allowedSorts ?? '').isNotEmpty) { 46 | directions = allowedSorts.split(',').map((s) => s.trim()).toList(growable: false); 47 | } else { 48 | directions = directionMap.keys.toList(growable: false); 49 | } 50 | if (directions.isEmpty || directions.any((s) => directionMap[s] == null)) { 51 | throw ArgumentError( 52 | 'SkawaDataTableSortDirective accepts only "asc" and/or "desc" as sort directions. Use comma separated values for both directions.'); 53 | } 54 | column.sortModel = SortModel(directions.map((s) => directionMap[s]).toList(growable: false)); 55 | } 56 | 57 | @Input() 58 | set initialSort(String sort) { 59 | if (directionMap[sort] == null) { 60 | throw ArgumentError('SkawaDataTableSortDirective initial sort value can only be "asc" or "desc"'); 61 | } 62 | column.sortModel.activeSort = directionMap[sort]; 63 | } 64 | 65 | static const String asc = 'asc'; 66 | static const String desc = 'desc'; 67 | 68 | static const Map directionMap = const {asc: SortDirection.asc, desc: SortDirection.desc}; 69 | } 70 | -------------------------------------------------------------------------------- /skawa_material_components/lib/grid/grid_component.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:html'; 3 | import 'package:angular/angular.dart'; 4 | import '../base_implementations/grid/grid.dart'; 5 | 6 | /// __Inputs__: 7 | /// 8 | /// - `gridGutter`: padding between grid items 9 | /// 10 | @Component( 11 | selector: 'skawa-grid', 12 | template: '
', 13 | styleUrls: ['grid_component.css'], 14 | directives: [GridTileDirective], 15 | changeDetection: ChangeDetectionStrategy.OnPush) 16 | class GridComponent extends GridBase implements AfterViewInit, OnInit { 17 | List _tiles; 18 | 19 | @HostBinding('style.visibility') 20 | String visibility; 21 | 22 | @ContentChildren(GridTileDirective) 23 | set tiles(List tiles) { 24 | _tiles = tiles; 25 | updateAndDisplay(true); 26 | } 27 | 28 | @Input() 29 | String gridGutter = '16'; 30 | 31 | @override 32 | bool get visible => visibility != 'hidden'; 33 | 34 | @override 35 | set visible(bool val) => visibility = val ? '' : 'hidden'; 36 | 37 | @ViewChild('grid') 38 | HtmlElement grid; 39 | 40 | List get tiles => _tiles; 41 | 42 | @override 43 | void updateAndDisplay(bool forceRefresh) { 44 | _resizeTimer?.cancel(); 45 | _resizeTimer = Timer(Duration(milliseconds: 100), () { 46 | // there are no tiles to update, return 47 | if (tiles.isEmpty) return; 48 | var gridWidth = grid.clientWidth; 49 | // width did not change, return 50 | if (_previousWidth == gridWidth && !forceRefresh) return; 51 | _previousWidth = gridWidth; 52 | 53 | GridUpdate gridUpdate = calculateGridUpdate(gridWidth, gutterSize: int.parse(gridGutter)); 54 | visible = true; 55 | grid.style..height = '${gridUpdate.gridHeight}px'; 56 | for (int i = 0; i < gridUpdate.tilePositions.length; ++i) { 57 | Point newPosition = gridUpdate.tilePositions[i]; 58 | tiles.elementAt(i).reposition(newPosition); 59 | } 60 | }); 61 | } 62 | 63 | @override 64 | void ngAfterViewInit() => updateAndDisplay(true); 65 | 66 | @override 67 | void ngOnInit() { 68 | window.onResize.listen((_) { 69 | updateAndDisplay(false); 70 | }); 71 | } 72 | 73 | Timer _resizeTimer; 74 | int _previousWidth = -1; 75 | } 76 | 77 | @Directive(selector: '[gridTile]') 78 | class GridTileDirective extends Object with DomTransformReposition implements GridTile { 79 | @override 80 | final HtmlElement tile; 81 | 82 | GridTileDirective(this.tile); 83 | } 84 | -------------------------------------------------------------------------------- /skawa_components/test/ckeditor_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'dart:convert' show htmlEscape; 3 | import 'package:angular/angular.dart'; 4 | import 'package:angular_test/angular_test.dart'; 5 | import 'package:skawa_components/ckeditor/ckeditor.dart'; 6 | import 'package:test/test.dart'; 7 | import 'package:html_unescape/html_unescape.dart'; 8 | 9 | import 'ckeditor_test.template.dart' as ng; 10 | 11 | void main() { 12 | ng.initReflector(); 13 | tearDown(disposeAnyRunningTest); 14 | test('CKEditor', () async { 15 | var bed = NgTestBed(); 16 | var fixture = await bed.create(); 17 | await fixture.update(); 18 | expect(fixture.assertOnlyInstance.editor.editorName, "editor"); 19 | expect(fixture.assertOnlyInstance.editor.extraPlugins, allOf(isList, hasLength(1))); 20 | expect(fixture.assertOnlyInstance.editor.extraPlugins.first, predicate((ExtraPlugin plugin) { 21 | return plugin.path == '/ckeditor/dartlogo/plugin.js' && plugin.name == 'dartlogo' && plugin.entrypoint == ''; 22 | })); 23 | expect(fixture.assertOnlyInstance.editor.configUrl, '/dartlogo/config.js'); 24 | expect(HtmlUnescape().convert(fixture.assertOnlyInstance.editor.value), TestEditorComponent._TEST_MARKUP); 25 | }); 26 | test('CKEditorConfig', () async { 27 | var bed = NgTestBed(); 28 | var fixture = await bed.create(); 29 | await fixture.update(); 30 | expect(fixture.assertOnlyInstance.editor.config['language'], 'en'); 31 | }); 32 | } 33 | 34 | @Component(selector: 'dummy-cke', template: ''' 35 | 39 | 40 | ''', directives: [SkawaCkeditorComponent]) 41 | class TestEditorComponent { 42 | List plugins = [ExtraPlugin('dartlogo', '/ckeditor/dartlogo/plugin.js', '')]; 43 | 44 | @ViewChild(SkawaCkeditorComponent) 45 | SkawaCkeditorComponent editor; 46 | 47 | String get escaped => htmlEscape.convert(_TEST_MARKUP); 48 | 49 | static const String _TEST_MARKUP = 'Some
content

comes here.

'; 50 | } 51 | 52 | @Component(selector: 'config-cke', template: ''' 53 | 55 | 56 | ''', directives: [SkawaCkeditorComponent]) 57 | class ConfigEditorComponent { 58 | Map config = {'language': 'en'}; 59 | 60 | @ViewChild(SkawaCkeditorComponent) 61 | SkawaCkeditorComponent editor; 62 | } 63 | -------------------------------------------------------------------------------- /skawa_material_components/lib/skawa_banner/src/skawa_banner.scss: -------------------------------------------------------------------------------- 1 | @import 'package:angular_components/css/mdc_web/card/mdc-card'; 2 | @import 'package:skawa_material_components/css/_responsiveness'; 3 | 4 | :host { 5 | font-size: 14px; 6 | 7 | .color-info { 8 | color: #3f51b5; 9 | } 10 | 11 | .color-warning { 12 | color: rgb(251, 192, 45); 13 | } 14 | 15 | .color-error { 16 | color: rgb(255, 0, 0); 17 | } 18 | 19 | 20 | @keyframes fadeIn { 21 | 0% { 22 | opacity: 0; 23 | } 24 | 33% { 25 | opacity: 0; 26 | } 27 | 100% { 28 | opacity: 1; 29 | } 30 | } 31 | 32 | @keyframes fadeOut { 33 | 0% { 34 | opacity: 1; 35 | } 36 | 66% { 37 | opacity: 0; 38 | } 39 | 100% { 40 | opacity: 0; 41 | } 42 | } 43 | 44 | section.banner { 45 | width: 100%; 46 | position: sticky; 47 | display: flex; 48 | flex-direction: row; 49 | justify-content: space-between; 50 | z-index: 900; 51 | top: 0; 52 | max-height: 0; 53 | padding: 0 8px 0 16px; 54 | opacity: 0; 55 | transition: max-height 0.3s ease, padding-top 0.3s ease, padding-bottom 0.3s ease; 56 | animation: fadeOut 0.3s; 57 | 58 | &.actions-below { 59 | flex-direction: column; 60 | } 61 | 62 | &.show { 63 | max-height: 72px; 64 | padding: 10px 8px 8px 16px; 65 | opacity: 1; 66 | animation: fadeIn 0.3s; 67 | 68 | div.message { 69 | padding-right: 36px; 70 | 71 | @include tablet() { 72 | padding-right: 90px; 73 | } 74 | } 75 | 76 | &.actions-below { 77 | max-height: 120px; 78 | padding: 24px 8px 8px 16px; 79 | 80 | div.message { 81 | padding-right: 16px; 82 | } 83 | 84 | div.actions { 85 | padding-top: 12px; 86 | } 87 | 88 | } 89 | } 90 | 91 | div.message { 92 | display: flex; 93 | justify-content: center; 94 | align-items: center; 95 | padding: 0; 96 | 97 | span { 98 | display: -webkit-box; 99 | -webkit-box-orient: vertical; 100 | -webkit-line-clamp: 2; 101 | overflow: hidden; 102 | } 103 | 104 | material-icon { 105 | padding: 0 16px 0 16px; 106 | @include tablet() { 107 | padding: 0 24px 0 16px; 108 | } 109 | } 110 | } 111 | 112 | div.actions { 113 | display: flex; 114 | justify-content: flex-end; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /skawa_components/lib/nav_item/nav_item.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | 3 | import 'package:angular/angular.dart'; 4 | 5 | import 'package:angular_components/button_decorator/button_decorator.dart'; 6 | import 'package:angular_components/material_button/material_button.dart'; 7 | import 'package:angular_components/material_button/material_button_base.dart'; 8 | import 'package:angular_components/material_ripple/material_ripple.dart'; 9 | 10 | import '../sidebar_item/sidebar_item.dart'; 11 | 12 | /// Component compositing [SkawaSidebarItemComponent] and [MaterialButtonBase] 13 | /// 14 | /// A Toolbar item that is also a button. Only a small subset of Material 15 | /// Buttons features are exposed. 16 | /// 17 | /// Most properties, events and attributes are the same as [MaterialButtonComponent]'s. 18 | /// Please see it's documentation for general options. 19 | /// 20 | /// __Example usage:__ 21 | /// Nav text 22 | /// Nav item with icon 23 | /// Event 24 | /// 25 | /// __Properties:__ 26 | /// - `icon: String` -- Icon name to display. 27 | /// 28 | /// __Events:__ 29 | /// - `trigger: Event` -- Published when the nav item is activated with click 30 | /// or keypress. 31 | /// 32 | /// __Attributes:__ 33 | /// - `textOnly` -- If present, `icon` will be ignored and it's place removed. 34 | /// 35 | @Component( 36 | selector: 'skawa-nav-item', 37 | templateUrl: 'nav_item.html', 38 | styleUrls: ['nav_item.css'], 39 | directives: [SkawaSidebarItemComponent, MaterialRippleComponent, NgClass], 40 | changeDetection: ChangeDetectionStrategy.OnPush, 41 | providers: [Provider(ButtonDirective, useExisting: SkawaNavItemComponent)]) 42 | class SkawaNavItemComponent extends MaterialButtonBase with TextOnlyMixin { 43 | final ChangeDetectorRef _changeDetector; 44 | 45 | bool hovering = false; 46 | 47 | /// MaterialIcon name to use as icon 48 | @Input() 49 | String icon; 50 | 51 | @HostBinding('attr.fullWidth') 52 | @Input() 53 | bool fullWidth; 54 | 55 | SkawaNavItemComponent(HtmlElement element, this._changeDetector, @Attribute('role') String role) 56 | : super(element, role); 57 | 58 | Map get ngClasses => {'hovering': hovering, 'icon-padding': icon == null}; 59 | 60 | @override 61 | void focusedStateChanged() => _changeDetector.markForCheck(); 62 | 63 | @HostListener('mouseenter') 64 | void onMouseEnter() => hovering = true; 65 | 66 | @HostListener('mouseout') 67 | void onMouseOut() => hovering = false; 68 | } 69 | -------------------------------------------------------------------------------- /skawa_components/lib/prompt/prompt.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:html'; 3 | import 'package:angular/angular.dart'; 4 | import 'package:angular_components/material_dialog/material_dialog.dart'; 5 | import 'package:angular_components/material_yes_no_buttons/material_yes_no_buttons.dart'; 6 | import 'package:angular_components/laminate/components/modal/modal.dart'; 7 | 8 | /// A prompt component that asks a yes or no question from the user inside a modal. 9 | /// You can specify callbacks for both cases, a question, custom yes-no strings, . 10 | /// and also when should the modal be visible or not. 11 | /// 12 | ///__Example__ 13 | /// 14 | /// 21 | /// 22 | /// 23 | @Component( 24 | selector: 'skawa-prompt', 25 | templateUrl: 'prompt.html', 26 | directives: [MaterialDialogComponent, ModalComponent, MaterialYesNoButtonsComponent], 27 | changeDetection: ChangeDetectionStrategy.OnPush) 28 | class SkawaPromptComponent { 29 | final ChangeDetectorRef _cd; 30 | 31 | bool pending = false; 32 | 33 | @Input('yes') 34 | Function yesCallback; 35 | 36 | @Input('no') 37 | Function noCallback; 38 | 39 | @Input() 40 | String message; 41 | 42 | @Input() 43 | String textYes = _textYes; 44 | 45 | @Input() 46 | String textNo = _textNo; 47 | 48 | @Input('visible') 49 | bool isVisible = false; 50 | 51 | // Only here for testing purposes 52 | @ViewChild('messageText') 53 | HtmlElement messageText; 54 | 55 | @ViewChild(ModalComponent) 56 | ModalComponent modal; 57 | 58 | //Only here for testing purposes 59 | @ViewChild(MaterialYesNoButtonsComponent) 60 | MaterialYesNoButtonsComponent yesNoButtonsComponent; 61 | 62 | SkawaPromptComponent(this._cd); 63 | 64 | void yes() { 65 | if (yesCallback == null) return; 66 | var yesReturn = yesCallback(); 67 | if (yesReturn is Future) { 68 | pending = true; 69 | _cd.markForCheck(); 70 | yesReturn.then((_) { 71 | pending = false; 72 | _cd.markForCheck(); 73 | }); 74 | } 75 | } 76 | 77 | void no() { 78 | if (noCallback == null) return; 79 | var noReturn = noCallback(); 80 | if (noReturn is Future) { 81 | pending = true; 82 | _cd.markForCheck(); 83 | noReturn.then((_) { 84 | pending = false; 85 | _cd.markForCheck(); 86 | }); 87 | } 88 | } 89 | 90 | static final String _textYes = "Yes"; 91 | static final String _textNo = "No"; 92 | } 93 | -------------------------------------------------------------------------------- /skawa_material_components/test/data_table_po/data_table_po.dart: -------------------------------------------------------------------------------- 1 | import 'package:pageloader/pageloader.dart'; 2 | 3 | part 'data_table_po.g.dart'; 4 | 5 | @PageObject() 6 | abstract class DatatablePO { 7 | DatatablePO(); 8 | 9 | factory DatatablePO.create(PageLoaderElement context) = $DatatablePO.create; 10 | 11 | @ByTagName('table') 12 | TablePO get table; 13 | } 14 | 15 | @PageObject() 16 | abstract class TablePO { 17 | TablePO(); 18 | 19 | factory TablePO.create(PageLoaderElement context) = $TablePO.create; 20 | 21 | @root 22 | PageLoaderElement get rootElement; 23 | 24 | @ByTagName('thead') 25 | TableHeaderSectionPO get thead; 26 | 27 | @ByTagName('tbody') 28 | TableSectionPO get tbody; 29 | 30 | @ByTagName('tfoot') 31 | TableSectionPO get tfoot; 32 | } 33 | 34 | @PageObject() 35 | abstract class TableHeaderSectionPO { 36 | TableHeaderSectionPO(); 37 | 38 | factory TableHeaderSectionPO.create(PageLoaderElement context) = $TableHeaderSectionPO.create; 39 | 40 | @ByTagName('tr') 41 | TableHeaderRowPO get tr; 42 | } 43 | 44 | @PageObject() 45 | abstract class TableHeaderRowPO { 46 | TableHeaderRowPO(); 47 | 48 | factory TableHeaderRowPO.create(PageLoaderElement context) = $TableHeaderRowPO.create; 49 | 50 | @ByTagName('th') 51 | List get th; 52 | } 53 | 54 | @PageObject() 55 | abstract class TableSectionPO { 56 | TableSectionPO(); 57 | 58 | factory TableSectionPO.create(PageLoaderElement context) = $TableSectionPO.create; 59 | 60 | @ByTagName('tr') 61 | List get tr; 62 | } 63 | 64 | @PageObject() 65 | abstract class TableRowPO { 66 | TableRowPO(); 67 | 68 | factory TableRowPO.create(PageLoaderElement context) = $TableRowPO.create; 69 | 70 | @root 71 | PageLoaderElement get rootElement; 72 | 73 | @ByTagName('td') 74 | List get td; 75 | 76 | String get classes => rootElement.attributes['class']; 77 | } 78 | 79 | @PageObject() 80 | abstract class TableCellPO { 81 | TableCellPO(); 82 | 83 | factory TableCellPO.create(PageLoaderElement context) = $TableCellPO.create; 84 | 85 | @root 86 | PageLoaderElement get rootElement; 87 | 88 | @ByTagName('material-checkbox') 89 | PageLoaderElement get materialCheckbox; 90 | } 91 | 92 | @PageObject() 93 | abstract class TableHeaderCellPO { 94 | TableHeaderCellPO(); 95 | 96 | factory TableHeaderCellPO.create(PageLoaderElement context) = $TableHeaderCellPO.create; 97 | 98 | @root 99 | PageLoaderElement get rootElement; 100 | 101 | @ByTagName('material-checkbox') 102 | PageLoaderElement get materialCheckbox; 103 | 104 | @ByTagName('a') 105 | PageLoaderElement get sortLink; 106 | } 107 | -------------------------------------------------------------------------------- /skawa_components/lib/feature_toggle/feature_toggle.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/angular.dart' show TemplateRef, ViewContainerRef, Directive, Input, Optional; 2 | import 'package:meta/meta.dart' show visibleForTesting; 3 | 4 | /// Base class for FeatureToggle directives that is queried 5 | /// if a feature is enabled or not. 6 | /// 7 | /// Used by [FeatureToggleEnabledDirective] & [FeatureToggleDisabledDirective] 8 | abstract class FeatureToggleService { 9 | /// Determines if a feature is enabled or not 10 | bool isEnabled(String featureName); 11 | } 12 | 13 | @visibleForTesting 14 | abstract class FeatureToggleBase { 15 | final FeatureToggleService toggleService; 16 | 17 | FeatureToggleBase(this.toggleService); 18 | 19 | bool shouldDisplay(String featureName); 20 | 21 | /// Inserts or removes [_templateRef] based on the enabled state of a feature 22 | void toggleDisplay(String name, TemplateRef _templateRef, ViewContainerRef _viewContainer) { 23 | final isFeatureShown = shouldDisplay(name); 24 | // if feature is the same and its state is the same as previously, 25 | // do nothing 26 | if (name == _previousFeature && isFeatureShown == _previouslyShown) { 27 | return; 28 | } 29 | if (isFeatureShown) { 30 | _viewContainer.clear(); 31 | _viewContainer.createEmbeddedView(_templateRef); 32 | } else { 33 | _viewContainer.clear(); 34 | } 35 | _previouslyShown = isFeatureShown; 36 | _previousFeature = name; 37 | } 38 | 39 | bool _previouslyShown; 40 | String _previousFeature; 41 | } 42 | 43 | @Directive( 44 | selector: '[featureEnabled]', 45 | ) 46 | class FeatureToggleEnabledDirective extends FeatureToggleBase { 47 | final TemplateRef _templateRef; 48 | final ViewContainerRef _viewContainer; 49 | 50 | FeatureToggleEnabledDirective(this._templateRef, this._viewContainer, FeatureToggleService toggleService) 51 | : super(toggleService); 52 | 53 | @Input('featureEnabled') 54 | set featureName(String name) { 55 | toggleDisplay(name, _templateRef, _viewContainer); 56 | } 57 | 58 | @override 59 | bool shouldDisplay(String featureName) { 60 | return toggleService.isEnabled(featureName); 61 | } 62 | } 63 | 64 | @Directive( 65 | selector: '[featureDisabled]', 66 | ) 67 | class FeatureToggleDisabledDirective extends FeatureToggleBase { 68 | final TemplateRef _templateRef; 69 | final ViewContainerRef _viewContainer; 70 | 71 | FeatureToggleDisabledDirective(this._templateRef, this._viewContainer, FeatureToggleService toggleService) 72 | : super(toggleService); 73 | 74 | @Input('featureDisabled') 75 | set featureName(String name) { 76 | toggleDisplay(name, _templateRef, _viewContainer); 77 | } 78 | 79 | @override 80 | bool shouldDisplay(String featureName) { 81 | return !toggleService.isEnabled(featureName); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /skawa_material_components/lib/data_table/data_table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 22 | 23 | 24 | 25 | 32 | 39 | 55 | 56 | 57 | 58 | 59 | 64 | 65 | 66 |
5 | 10 | 11 | 17 | 18 | 21 |
33 | 37 | 38 | 42 | 48 | 53 | 54 |
62 | {{c.footer}} 63 |
67 | -------------------------------------------------------------------------------- /skawa_components/test/sidebar_item_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:angular/angular.dart'; 3 | import 'package:angular_test/angular_test.dart'; 4 | import 'package:pageloader/html.dart'; 5 | import 'package:skawa_components/sidebar_item/sidebar_item.dart'; 6 | import 'package:test/test.dart'; 7 | import 'sidebar_item_test.template.dart' as ng; 8 | 9 | part 'sidebar_item_test.g.dart'; 10 | 11 | void main() { 12 | ng.initReflector(); 13 | tearDown(disposeAnyRunningTest); 14 | group('SidebarItem | ', () { 15 | test('initialization with zero input', () async { 16 | final fixture = await NgTestBed().create(); 17 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 18 | final pageObject = TestPO.create(context); 19 | expect(pageObject.sideBarItemList.span.classes.contains('text-only'), isFalse); 20 | expect(pageObject.sideBarItemList.rootElement.attributes['textOnly'], isNull); 21 | }); 22 | test('initialization with icon', () async { 23 | final fixture = await NgTestBed() 24 | .create(beforeChangeDetection: (testElement) => testElement.icon = 'alarm'); 25 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 26 | final pageObject = TestPO.create(context); 27 | expect(pageObject.sideBarItemList.materialIcon.rootElement.innerText, 'alarm'); 28 | expect(pageObject.sideBarItemList.span.classes.contains('text-only'), isFalse); 29 | expect(pageObject.sideBarItemList.rootElement.attributes['textOnly'], isNull); 30 | }); 31 | test('initialization with icon but with textOnly', () async { 32 | final fixture = await NgTestBed().create(beforeChangeDetection: (testElement) { 33 | testElement 34 | ..icon = 'alarm' 35 | ..textOnly = ''; 36 | }); 37 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 38 | final pageObject = TestPO.create(context); 39 | expect(pageObject.sideBarItemList.span.classes.contains('text-only'), isTrue); 40 | expect(pageObject.sideBarItemList.rootElement.attributes['textOnly'], isNotNull); 41 | }); 42 | }); 43 | } 44 | 45 | @Component( 46 | selector: 'test', 47 | template: '', 48 | directives: [SkawaSidebarItemComponent]) 49 | class SidebarItemTestComponent { 50 | String textOnly; 51 | String icon; 52 | } 53 | 54 | @PageObject() 55 | @CheckTag('test') 56 | abstract class TestPO { 57 | TestPO(); 58 | 59 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 60 | 61 | @ByTagName('skawa-sidebar-item') 62 | SidebarItemPO get sideBarItemList; 63 | } 64 | 65 | @PageObject() 66 | abstract class SidebarItemPO { 67 | SidebarItemPO(); 68 | 69 | factory SidebarItemPO.create(PageLoaderElement context) = $SidebarItemPO.create; 70 | 71 | @root 72 | PageLoaderElement get rootElement; 73 | 74 | @ByTagName('span') 75 | PageLoaderElement get span; 76 | 77 | @ByTagName('material-icon') 78 | MaterialIconPO get materialIcon; 79 | } 80 | 81 | @PageObject() 82 | abstract class MaterialIconPO { 83 | MaterialIconPO(); 84 | 85 | factory MaterialIconPO.create(PageLoaderElement context) = $MaterialIconPO.create; 86 | 87 | @root 88 | PageLoaderElement get rootElement; 89 | } 90 | -------------------------------------------------------------------------------- /skawa_components/test/nav_item_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:angular/angular.dart'; 3 | import 'package:angular_test/angular_test.dart'; 4 | import 'package:pageloader/html.dart'; 5 | import 'package:skawa_components/nav_item/nav_item.dart'; 6 | import 'package:test/test.dart'; 7 | import 'nav_item_test.template.dart' as ng; 8 | 9 | part 'nav_item_test.g.dart'; 10 | 11 | void main() { 12 | ng.initReflector(); 13 | tearDown(disposeAnyRunningTest); 14 | final testBed = NgTestBed(); 15 | NgTestFixture fixture; 16 | TestPO pageObject; 17 | group('NavItem | ', () { 18 | setUp(() async { 19 | fixture = await testBed.create(); 20 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 21 | pageObject = TestPO.create(context); 22 | }); 23 | test('initialization with zero input', () async { 24 | expect(pageObject.sideNavItemList.rootElement.innerText, isEmpty); 25 | expect(pageObject.sideNavItemList.sidebarItemList.span.classes.contains('text-only'), isFalse); 26 | expect(pageObject.sideNavItemList.rootElement.attributes['textOnly'], isNull); 27 | }); 28 | test('initialization with icon', () async { 29 | await fixture.update((testElement) => testElement.icon = 'alarm'); 30 | expect((pageObject.sideNavItemList.sidebarItemList.materialIcon).rootElement.innerText, 'alarm'); 31 | expect(pageObject.sideNavItemList.sidebarItemList.span.classes.contains('text-only'), isFalse); 32 | expect(pageObject.sideNavItemList.rootElement.attributes['textOnly'], isNull); 33 | }); 34 | test('initialization with icon but with textOnly', () async { 35 | await fixture.update((testElement) { 36 | testElement 37 | ..icon = 'alarm' 38 | ..textOnly = ''; 39 | }); 40 | expect(pageObject.sideNavItemList.sidebarItemList.span.classes.contains('text-only'), isTrue); 41 | expect(pageObject.sideNavItemList.rootElement.attributes['textOnly'], isNotNull); 42 | }); 43 | }); 44 | } 45 | 46 | @Component(selector: 'test', template: ''' 47 | 48 | ''', directives: [SkawaNavItemComponent]) 49 | class NavItemTestComponent { 50 | String textOnly; 51 | String icon; 52 | } 53 | 54 | @PageObject() 55 | @CheckTag('test') 56 | abstract class TestPO { 57 | TestPO(); 58 | 59 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 60 | 61 | @ByTagName('skawa-nav-item') 62 | NavItemPO get sideNavItemList; 63 | } 64 | 65 | @PageObject() 66 | abstract class NavItemPO { 67 | NavItemPO(); 68 | 69 | factory NavItemPO.create(PageLoaderElement context) = $NavItemPO.create; 70 | 71 | @root 72 | PageLoaderElement get rootElement; 73 | 74 | @ByTagName('skawa-sidebar-item') 75 | SidebarItemPO get sidebarItemList; 76 | } 77 | 78 | @PageObject() 79 | abstract class SidebarItemPO { 80 | SidebarItemPO(); 81 | 82 | factory SidebarItemPO.create(PageLoaderElement context) = $SidebarItemPO.create; 83 | 84 | @ByTagName('span') 85 | PageLoaderElement get span; 86 | 87 | @ByTagName('material-icon') 88 | MaterialIconPO get materialIcon; 89 | } 90 | 91 | @PageObject() 92 | abstract class MaterialIconPO { 93 | MaterialIconPO(); 94 | 95 | factory MaterialIconPO.create(PageLoaderElement context) = $MaterialIconPO.create; 96 | 97 | @root 98 | PageLoaderElement get rootElement; 99 | } 100 | -------------------------------------------------------------------------------- /skawa_material_components/test/base_grid_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'dart:html'; 3 | import 'package:skawa_material_components/base_implementations/grid/grid.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('GridUpdate | ', () { 8 | test('constructor', () { 9 | List> pointList = [Point(12, 13), Point(13122, 1), Point(412, 163), Point(312, 153), Point(212, 7613)]; 10 | GridUpdate gridUpdate = GridUpdate(pointList, 116); 11 | expect(gridUpdate.tilePositions, pointList); 12 | expect(gridUpdate.gridHeight, 116); 13 | }); 14 | }); 15 | group('GridTile | ', () { 16 | Element tileElement; 17 | setUp(() { 18 | tileElement = Element.div(); 19 | tileElement.style.height = '116px'; 20 | tileElement.style.width = '16px'; 21 | document.body.append(tileElement); 22 | }); 23 | test('constructor', () { 24 | GridTile gridTile = GridTile(tileElement); 25 | expect(gridTile.height, 116); 26 | expect(gridTile.width, 16); 27 | }); 28 | test('reposition method', () async { 29 | GridTile gridTile = GridTile(tileElement); 30 | Point point = Point(321, 543); 31 | gridTile.reposition(point); 32 | expect(gridTile.height, 116); 33 | expect(gridTile.width, 16); 34 | expect(tileElement.style.transform, 'translate(${point.x}px, ${point.y}px)'); 35 | }); 36 | }); 37 | group('Grid | ', () { 38 | List tileList; 39 | var tileElementList; 40 | Element gridElement; 41 | setUp(() { 42 | tileList = []; 43 | tileElementList = []; 44 | gridElement = Element.div(); 45 | gridElement.style.height = '116px'; 46 | gridElement.style.width = '48px'; 47 | document.body.append(gridElement); 48 | for (int i = 0; i < 5; i++) { 49 | Element tileElement = Element.div(); 50 | tileElement.style.height = '116px'; 51 | tileElement.style.width = '16px'; 52 | document.body.append(tileElement); 53 | tileElementList.add(tileElement); 54 | tileList.add(GridTile(tileElement)); 55 | } 56 | }); 57 | test('constructor', () { 58 | Grid grid = Grid(gridElement, tileList); 59 | expect(grid.tiles, tileList); 60 | expect(grid.visible, isTrue); 61 | }); 62 | test('calculateGridUpdate method', () { 63 | Grid grid = Grid(gridElement, tileList); 64 | GridUpdate gridUpdate = grid.calculateGridUpdate(64); 65 | expect(grid.tiles, tileList); 66 | expect(grid.visible, isTrue); 67 | expect(gridUpdate.gridHeight, 412); 68 | expect(gridUpdate.tilePositions.length, grid.tiles.length); 69 | expect(gridUpdate.tilePositions, [Point(-8, 0), Point(24, 0), Point(-8, 132), Point(24, 132), Point(-8, 264)]); 70 | }); 71 | test('updateAndDisplay method', () { 72 | Grid grid = Grid(gridElement, tileList); 73 | grid.visible = false; 74 | grid.updateAndDisplay(true); 75 | List actual = []; 76 | List expected = []; 77 | GridUpdate gridUpdate = grid.calculateGridUpdate(48); 78 | for (int i = 0; i < gridUpdate.tilePositions.length; i++) { 79 | expected.add('translate(${gridUpdate.tilePositions[i].x}px, ${gridUpdate.tilePositions[i].y}px)'); 80 | actual.add(tileElementList[i].style.transform as String); 81 | } 82 | expect(grid.tiles, tileList); 83 | expect(grid.visible, isTrue); 84 | expect(gridElement.clientHeight, 676); 85 | expect(actual, expected); 86 | }); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /skawa_components/lib/ckeditor/ckeditor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:js_util'; 3 | 4 | import 'package:angular/angular.dart'; 5 | import 'package:js/js.dart'; 6 | 7 | import 'ckeditor_interop.dart' as js_ck; 8 | 9 | /// Simple CKEditor wrapper component. 10 | /// 11 | /// *Note:* CKEditor component uses ckeditor.js, make sure it is available, this component compatible with the 4.* version 12 | /// 13 | /// __Example usage:__ 14 | /// 15 | /// 19 | /// 20 | /// 21 | /// __Events:__ 22 | /// - `change: String` -- Triggered when editor content changes 23 | /// 24 | /// __Properties:__ 25 | /// - `editorName: String` -- element CSS selector to replace with CKEditor, this should be unique 26 | /// - `extraPlugins: List` -- extra plugins to load with CKEditor 27 | /// - `configUrl: String` -- url of the config file to load for CKEditor 28 | /// - `content: String` -- initial value of editor 29 | /// 30 | @Component(selector: 'skawa-ckeditor', templateUrl: 'ckeditor.html', changeDetection: ChangeDetectionStrategy.OnPush) 31 | class SkawaCkeditorComponent implements AfterViewInit, OnDestroy { 32 | final _changeController = StreamController.broadcast(); 33 | _CKEditor _ckeditor; 34 | 35 | @Input() 36 | String editorName; 37 | 38 | @Input() 39 | List extraPlugins; 40 | 41 | @Input() 42 | String content; 43 | 44 | @Input() 45 | Map config; 46 | 47 | @Input() 48 | String configUrl; 49 | 50 | @Output('change') 51 | Stream get onChange => _changeController.stream; 52 | 53 | String get value => _ckeditor.getEditorData(); 54 | 55 | @override 56 | void ngAfterViewInit() { 57 | _ckeditor ??= _CKEditor(editorName, extraPlugins: extraPlugins, configUrl: configUrl, config: config); 58 | _ckeditor.on('change', (_) { 59 | _changeController.add(value); 60 | }); 61 | } 62 | 63 | @override 64 | void ngOnDestroy() { 65 | _changeController.close(); 66 | } 67 | } 68 | 69 | /// Extra plugin to load with for CKEditor 70 | class ExtraPlugin { 71 | final String name; 72 | final String path; 73 | final String entrypoint; 74 | 75 | ExtraPlugin(this.name, this.path, this.entrypoint); 76 | } 77 | 78 | class _CKEditor { 79 | js_ck.CKEditorInstance _jsEditorInstance; 80 | 81 | _CKEditor(String editorElementSelector, 82 | {Iterable extraPlugins = const [], 83 | String configUrl = '/ckeditor/config.js', 84 | Map config}) { 85 | /// add external plugins 86 | _maybeAddExtraPlugins(extraPlugins); 87 | 88 | Map _config = config ?? {'customConfig': configUrl}; 89 | 90 | /// Load editor 91 | _jsEditorInstance = js_ck.CKEditor.replace(editorElementSelector, jsify(_config)); 92 | } 93 | 94 | String getEditorData() { 95 | return _jsEditorInstance.getData(); 96 | } 97 | 98 | void on(String eventName, js_ck.EventCallback callback) { 99 | return _jsEditorInstance.on(eventName, allowInterop(callback)); 100 | } 101 | 102 | void _maybeAddExtraPlugins(Iterable extraPlugins) { 103 | if (extraPlugins == null) return; 104 | for (ExtraPlugin extraPlugin in extraPlugins) { 105 | js_ck.addExternalPlugin(extraPlugin.name, extraPlugin.path, extraPlugin.entrypoint); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /skawa_material_components/lib/data_table/data_table.scss: -------------------------------------------------------------------------------- 1 | @import "package:skawa_material_components/css/skawa-material"; 2 | 3 | :host { 4 | display: block; 5 | background-color: white; 6 | 7 | table { 8 | width: 100%; 9 | box-sizing: border-box; 10 | 11 | &.selectable { 12 | th, td { 13 | &:nth-child(2) { 14 | padding-left: 0; 15 | } 16 | 17 | &:first-of-type { 18 | text-align: center; 19 | color: black; 20 | width: 72px; 21 | 22 | material_checkbox { 23 | margin: 0; 24 | width: 24px; 25 | height: 24px; 26 | vertical-align: middle; 27 | } 28 | } 29 | } 30 | 31 | tfoot { 32 | tr { 33 | td { 34 | &:first-of-type { 35 | text-align: right; 36 | padding-left: 72px !important; 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | &.non-selectable { 44 | th, td { 45 | &:first-of-type { 46 | padding-left: 24px !important; 47 | } 48 | } 49 | } 50 | 51 | tbody { 52 | tr { 53 | transition-duration: .28s; 54 | transition-timing-function: cubic-bezier(.4, 0, .2, 1); 55 | transition-property: background-color; 56 | 57 | &.selected { 58 | background-color: rgba(158, 158, 158, 0.3); 59 | } 60 | 61 | &.odd-row { 62 | background-color: rgba(158, 158, 158, 0.1); 63 | } 64 | 65 | &:hover, &.highlighted { 66 | background-color: rgba(158, 158, 158, 0.2); 67 | } 68 | } 69 | } 70 | 71 | tr { 72 | &:first-of-type, &:last-of-type { 73 | height: 56px; 74 | } 75 | 76 | border-bottom: 1px solid #d0d0d0; 77 | 78 | td, th { 79 | padding-left: 56px; 80 | text-align: right; 81 | vertical-align: middle; 82 | @include typo-body; 83 | height: 48px; 84 | 85 | &:first-of-type { 86 | padding-left: 0; 87 | } 88 | 89 | &:last-of-type { 90 | padding-right: 24px; 91 | } 92 | } 93 | 94 | td { 95 | font-size: 13px; 96 | } 97 | 98 | th { 99 | font-size: 12px; 100 | 101 | &.sort-enabled { 102 | a { 103 | cursor: pointer; 104 | 105 | &:before { 106 | font-family: 'Material Icons'; 107 | content: 'arrow_downwards'; 108 | opacity: 0; 109 | } 110 | 111 | &:hover { 112 | font-weight: bold; 113 | 114 | &:before { 115 | opacity: 0.38; 116 | } 117 | } 118 | } 119 | } 120 | 121 | &.sort { 122 | font-weight: bold; 123 | 124 | a { 125 | cursor: pointer; 126 | 127 | &:before { 128 | font-family: 'Material Icons'; 129 | content: 'arrow_upwards'; 130 | opacity: 0.87; 131 | } 132 | } 133 | 134 | &.desc { 135 | a:before { 136 | content: 'arrow_downwards'; 137 | } 138 | } 139 | } 140 | 141 | } 142 | } 143 | } 144 | 145 | .text-column, .text-column--header, .text-column--footer { 146 | text-align: left !important; 147 | } 148 | 149 | .numeric-column, .numeric-column--header, .numeric-column--footer { 150 | text-align: right; 151 | } 152 | } -------------------------------------------------------------------------------- /skawa_components/test/master_detail_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:angular/angular.dart'; 3 | import 'package:angular_test/angular_test.dart'; 4 | import 'package:pageloader/html.dart'; 5 | import 'package:skawa_components/master_detail/master_detail.dart'; 6 | import 'package:test/test.dart'; 7 | import 'master_detail_test.template.dart' as ng; 8 | 9 | part 'master_detail_test.g.dart'; 10 | 11 | void main() { 12 | ng.initReflector(); 13 | tearDown(disposeAnyRunningTest); 14 | final testBed = NgTestBed(); 15 | NgTestFixture fixture; 16 | TestPO pageObject; 17 | group('MasterDetail | ', () { 18 | setUp(() async { 19 | fixture = await testBed.create(); 20 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 21 | pageObject = TestPO.create(context); 22 | }); 23 | test('initialization', () async { 24 | expect(pageObject.sideMasterDetail.rootElement.attributes['expanded'], isNull); 25 | }); 26 | test('initialization then expand 1X', () async { 27 | await pageObject.expand.click(); 28 | expect(pageObject.sideMasterDetail.rootElement.attributes['expanded'], isEmpty); 29 | }); 30 | test('initialization then expand 2X', () async { 31 | await pageObject.expand.click(); 32 | await pageObject.expand.click(); 33 | expect(pageObject.sideMasterDetail.rootElement.attributes['expanded'], isEmpty); 34 | }); 35 | test('initialization then expand 1X then toggle 1X', () async { 36 | await pageObject.expand.click(); 37 | await pageObject.toggle.click(); 38 | expect(pageObject.sideMasterDetail.rootElement.attributes['expanded'], isNull); 39 | }); 40 | test('initialization then expand 1X then collapse 1X', () async { 41 | await pageObject.expand.click(); 42 | await pageObject.collapse.click(); 43 | expect(pageObject.sideMasterDetail.rootElement.attributes['expanded'], isNull); 44 | }); 45 | test('initialization then toogle 1X then collapse 1X', () async { 46 | await pageObject.toggle.click(); 47 | await pageObject.collapse.click(); 48 | expect(pageObject.sideMasterDetail.rootElement.attributes['expanded'], isNull); 49 | }); 50 | test('initialization then toggle 2X', () async { 51 | await pageObject.toggle.click(); 52 | await pageObject.toggle.click(); 53 | expect(pageObject.sideMasterDetail.rootElement.attributes['expanded'], isNull); 54 | }); 55 | }); 56 | } 57 | 58 | @Component(selector: 'test', template: ''' 59 | 60 | 61 | 62 | 63 | ''', directives: [SkawaMasterDetailComponent]) 64 | class MasterDetailTestComponent {} 65 | 66 | @PageObject() 67 | @CheckTag('test') 68 | abstract class TestPO { 69 | TestPO(); 70 | 71 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 72 | 73 | @ByCss('[expand-button]') 74 | PageLoaderElement get expand; 75 | 76 | @ByCss('[collapse-button]') 77 | PageLoaderElement get collapse; 78 | 79 | @ByCss('[toggle-button]') 80 | PageLoaderElement get toggle; 81 | 82 | @ByTagName('skawa-master-detail') 83 | MasterDetailPage get sideMasterDetail; 84 | } 85 | 86 | @PageObject() 87 | abstract class MasterDetailPage { 88 | MasterDetailPage(); 89 | 90 | factory MasterDetailPage.create(PageLoaderElement context) = $MasterDetailPage.create; 91 | 92 | @root 93 | PageLoaderElement get rootElement; 94 | } 95 | -------------------------------------------------------------------------------- /skawa_components/lib/code_mirror/code_mirror_mode_with_link.dart: -------------------------------------------------------------------------------- 1 | import 'code_mirror_mode.dart'; 2 | 3 | class CodeMirrorModeWithLink extends CodeMirrorMode { 4 | final String hint; 5 | final String lint; 6 | final String linterScript; 7 | final String linterJsScript; 8 | final List additionalModes; 9 | 10 | const CodeMirrorModeWithLink( 11 | {String mode, 12 | String modeName, 13 | String linter, 14 | String theme = 'eclipse', 15 | this.hint, 16 | this.lint, 17 | this.linterScript, 18 | this.linterJsScript, 19 | this.additionalModes}) 20 | : super(mode: mode, modeName: modeName, linter: linter, theme: theme); 21 | 22 | List get scriptList => [ 23 | if (mode != null) _modeCdnLink.replaceAll(_replaceString, mode), 24 | if (hint != null) _hintCdnLink.replaceAll(_replaceString, hint), 25 | if (additionalModes != null) 26 | ...additionalModes.fold>([], (list, mode) => list..addAll(mode.scriptList)) 27 | ]; 28 | 29 | List get linterScriptList => [ 30 | if (linterJsScript != null || lint != null) linterJsScript ?? _lintCdnLink.replaceAll(_replaceString, lint), 31 | if (linterScript != null) linterScript, 32 | if (additionalModes != null) 33 | ...additionalModes.fold>([], (list, mode) => list..addAll(mode.linterScriptList)) 34 | ]; 35 | 36 | static const String _replaceString = 'REPLACE'; 37 | static const String _hintCdnLink = 38 | 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/addon/hint/$_replaceString-hint.js'; 39 | static const String _lintCdnLink = 40 | 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/addon/lint/$_replaceString-lint.js'; 41 | static const String _modeCdnLink = 42 | 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/mode/$_replaceString/$_replaceString.js'; 43 | static const String cssLint = 'https://unpkg.com/csslint@1.0.5/dist/csslint.js'; 44 | 45 | static const String jsLint = 'https://codemirror.net/5/addon/lint/javascript-lint.js'; 46 | 47 | static const CodeMirrorModeWithLink javascript = const CodeMirrorModeWithLink( 48 | mode: 'javascript', 49 | hint: 'javascript', 50 | lint: 'javascript', 51 | linterScript: 'https://cdnjs.cloudflare.com/ajax/libs/jshint/2.9.5/jshint.min.js', 52 | linterJsScript: jsLint, 53 | linter: 'javascript'); 54 | static const CodeMirrorModeWithLink css = 55 | const CodeMirrorModeWithLink(mode: 'css', hint: 'css', lint: 'css', linterScript: cssLint, linter: 'css'); 56 | static const CodeMirrorModeWithLink html = const CodeMirrorModeWithLink( 57 | mode: 'htmlmixed', 58 | hint: 'html', 59 | lint: 'html', 60 | linterScript: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/addon/hint/html-hint.js', 61 | linter: 'html', 62 | additionalModes: const [javascript, css, xml, _json]); 63 | static const CodeMirrorModeWithLink xml = const CodeMirrorModeWithLink(mode: 'xml', hint: 'xml'); 64 | static const CodeMirrorModeWithLink json = const CodeMirrorModeWithLink( 65 | mode: 'javascript', 66 | modeName: 'application/json', 67 | hint: 'javascript', 68 | lint: 'javascript', 69 | linterScript: 'https://cdnjs.cloudflare.com/ajax/libs/jshint/2.9.5/jshint.min.js', 70 | linterJsScript: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.4/addon/lint/javascript-lint.js', 71 | linter: 'json', 72 | additionalModes: const [_json]); 73 | static const CodeMirrorModeWithLink _json = const CodeMirrorModeWithLink( 74 | lint: 'json', linterScript: 'https://cdnjs.cloudflare.com/ajax/libs/jsonlint/1.6.0/jsonlint.js'); 75 | } 76 | -------------------------------------------------------------------------------- /skawa_components/lib/markdown_editor/editor_render_source.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:html'; 3 | import 'package:angular/core.dart'; 4 | import 'package:angular_components/utils/async/async.dart'; 5 | 6 | /// Content source part of a SkawaEditor Component. It works in tandem 7 | /// with EditorRenderTarget directive. 8 | /// 9 | /// __Properties__: 10 | /// 11 | /// - `value: String` -- sets or gets the value of the editor element 12 | /// 13 | /// __Events__: 14 | /// 15 | /// - `update: String` -- emitted when value is changed (onInput), at most once every 500ms 16 | /// 17 | /// __Methods__: 18 | /// 19 | /// - `revertAllUpdates(): void` -- revert all updates since directiv e was created 20 | /// - `revertLastUpdate(): void` -- revert last update 21 | /// 22 | /// __Example__: 23 | /// 24 | /// 25 | /// 26 | @Directive(selector: '[editorRenderSource]', exportAs: 'editorRenderSource') 27 | class EditorRenderSource implements AfterViewInit, OnDestroy { 28 | final HtmlElement _htmlElement; 29 | final StreamController _onUpdatedController = StreamController.broadcast(); 30 | final List _changeStack = []; 31 | 32 | @Input() 33 | String initialValue; 34 | 35 | EditorRenderSource(this._htmlElement, @Optional() Duration updateDelay) { 36 | onUpdated = _onUpdatedController.stream.transform(debounceStream(updateDelay ?? _defaultTimeout)); 37 | } 38 | 39 | @Output('update') 40 | Stream onUpdated; 41 | 42 | String get _value { 43 | if (_htmlElement is TextAreaElement) { 44 | return (_htmlElement as TextAreaElement).value; 45 | } else if (_htmlElement is InputElement) { 46 | return (_htmlElement as InputElement).value; 47 | } else { 48 | return _htmlElement.nodeValue; 49 | } 50 | } 51 | 52 | set _value(String value) { 53 | if (_htmlElement is TextAreaElement) { 54 | (_htmlElement as TextAreaElement).value = value; 55 | } else if (_htmlElement is InputElement) { 56 | (_htmlElement as InputElement).value = value; 57 | } else { 58 | _htmlElement.setAttribute('value', value); 59 | } 60 | } 61 | 62 | String get value { 63 | if (initialValue != null && _changeStack.isEmpty) { 64 | return initialValue; 65 | } else { 66 | return _value; 67 | } 68 | } 69 | 70 | /// Sets the value of editor 71 | set value(String value) { 72 | _changeStack.insert(0, value); 73 | _value = value; 74 | } 75 | 76 | /// Gets the previous or initial value 77 | String get previousValue => _changeStack.isNotEmpty ? _changeStack.first : initialValue; 78 | 79 | void revertLastUpdate() { 80 | if (_changeStack.length <= 1) { 81 | revertAllUpdates(); 82 | } else { 83 | _changeStack.removeAt(0); 84 | _value = initialValue = _changeStack.first; 85 | _onUpdatedController.add(value); 86 | } 87 | } 88 | 89 | void revertAllUpdates() { 90 | value = initialValue; 91 | _changeStack.clear(); 92 | _onUpdatedController.add(initialValue); 93 | } 94 | 95 | @HostListener('input') 96 | void contentChanged(Event ev) { 97 | if (_changeStack.isEmpty || _changeStack.first != value) { 98 | _changeStack.insert(0, value); 99 | } 100 | _onUpdatedController.add(value); 101 | ev.stopPropagation(); 102 | } 103 | 104 | @override 105 | void ngAfterViewInit() { 106 | // sync initial value to DOM 107 | _onUpdatedController.add(initialValue); 108 | if (initialValue != null) _value = initialValue; 109 | _htmlElement.onInput.listen(contentChanged); 110 | } 111 | 112 | @override 113 | void ngOnDestroy() => _onUpdatedController.close(); 114 | 115 | static final Duration _defaultTimeout = Duration(milliseconds: 500); 116 | } 117 | -------------------------------------------------------------------------------- /skawa_material_components/lib/data_table/data_table_column.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:angular/core.dart'; 4 | import 'package:angular_components/model/ui/has_factory.dart'; 5 | 6 | import 'package:angular_components/model/ui/has_renderer.dart'; 7 | import 'row_data.dart'; 8 | import 'sort.dart'; 9 | 10 | export 'sort.dart'; 11 | 12 | typedef String DataTableAccessor(T rowData); 13 | 14 | /// A column of the SkawaDataTableComponent. Usable only with a SkawaDataTableComponent. 15 | /// 16 | /// __Example usage:__ 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// __Properties:__ 25 | /// 26 | /// - `accessor: bool` -- A function which return with the data to display in the cells. 27 | /// - `colRenderer: ComponentRenderer` -- component renderer function reference - if specified, accessor is ignored 28 | /// - `header: String` -- Header name of the column to display. 29 | /// - `footer: String` -- Footer name of the column to display. 30 | /// - `skipFooter: bool` -- Whether to display the footer. Defaults to true. 31 | /// 32 | /// __Outputs:__ 33 | /// 34 | /// - `trigger: RowData` -- Triggered when user clicks on text content of the cell. 35 | /// 36 | /// __Notes:__ 37 | /// `ComponentRenderer` is part of `package:angular_components`. It can be used to customize how a column is 38 | /// displayed allowing implementations to use custom components within the cell. Components must use `RendersValue` 39 | /// mixin. 40 | /// 41 | @Component( 42 | selector: 'skawa-data-table-col', 43 | template: '', 44 | directives: [SkawaDataColRendererDirective], 45 | changeDetection: ChangeDetectionStrategy.OnPush, 46 | visibility: Visibility.all) 47 | class SkawaDataTableColComponent implements OnInit, OnDestroy { 48 | final StreamController _triggerController = StreamController.broadcast(); 49 | final SkawaDataColRendererDirective columnRenderer; 50 | 51 | @Input() 52 | DataTableAccessor accessor; 53 | 54 | @Input() 55 | DataTableAccessor titleAccessor; 56 | 57 | @Input() 58 | String header; 59 | 60 | @Input() 61 | String footer; 62 | 63 | SortModel sortModel; 64 | 65 | /// If set to true, footer will not display this column and 66 | /// colspan of td element will be set accordingly 67 | @Input() 68 | bool skipFooter = true; 69 | 70 | @Input('class') 71 | String classString; 72 | 73 | SkawaDataTableColComponent(@Optional() @Self() this.columnRenderer); 74 | 75 | @Output('trigger') 76 | Stream get onTrigger => _triggerController.stream; 77 | 78 | bool get useAccessorAsLink => _triggerController.hasListener; 79 | 80 | bool get useColumnRenderer => columnRenderer?.factoryRenderer != null; 81 | 82 | void trigger(T row) { 83 | _triggerController.add(row); 84 | } 85 | 86 | Iterable getClasses([String suffix]) => 87 | classString?.trim()?.split(' ')?.map((className) => suffix != null ? '$className$suffix' : className); 88 | 89 | @override 90 | void ngOnDestroy() { 91 | _triggerController.close(); 92 | } 93 | 94 | @override 95 | void ngOnInit() { 96 | if (_triggerController.hasListener && useColumnRenderer) { 97 | throw ArgumentError('Cannot use [colRenderer] together with (trigger)'); 98 | } 99 | } 100 | } 101 | 102 | @Directive(selector: 'skawa-data-table-col[colRenderer]', visibility: Visibility.all) 103 | class SkawaDataColRendererDirective extends HasFactoryRenderer { 104 | @Input('colRenderer') 105 | // ignore: overridden_fields 106 | FactoryRenderer factoryRenderer; 107 | } 108 | -------------------------------------------------------------------------------- /skawa_components/test/prompt_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'dart:html'; 3 | import 'package:angular/angular.dart'; 4 | import 'package:angular_test/angular_test.dart'; 5 | import 'package:angular_components/laminate/popup/module.dart'; 6 | import 'package:skawa_components/prompt/prompt.dart'; 7 | import 'package:test/test.dart'; 8 | import 'package:pageloader/html.dart'; 9 | 10 | import 'prompt_test.template.dart' as ng; 11 | 12 | part 'prompt_test.g.dart'; 13 | 14 | void main() { 15 | ng.initReflector(); 16 | tearDown(disposeAnyRunningTest); 17 | final testBed = NgTestBed(); 18 | NgTestFixture fixture; 19 | TestPO pageObject; 20 | group('Prompt |', () { 21 | setUp(() async { 22 | fixture = await testBed.create(); 23 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 24 | pageObject = TestPO.create(context); 25 | }); 26 | test('initialization', () async { 27 | expect(pageObject.prompt, isNotNull); 28 | expect(fixture.assertOnlyInstance.prompt.messageText.text, ' Should you?'); 29 | expect(fixture.assertOnlyInstance.prompt.modal.visible, isTrue); 30 | }); 31 | test('calls yes function', () async { 32 | await fixture.update( 33 | (PromptTestComponent cmp) => cmp.prompt.yesNoButtonsComponent.yesButton..handleClick(MouseEvent('test'))); 34 | expect(fixture.assertOnlyInstance.prompt.messageText.text, ' Should you?'); 35 | expect(pageObject.messageSpan.innerText, 'Yes'); 36 | expect(fixture.assertOnlyInstance.prompt.modal.visible, isFalse); 37 | }); 38 | 39 | test('calls no function', () async { 40 | await fixture.update( 41 | (PromptTestComponent cmp) => cmp.prompt.yesNoButtonsComponent.noButton.handleClick(MouseEvent('test'))); 42 | expect(fixture.assertOnlyInstance.prompt.messageText.text, ' Should you?'); 43 | expect(pageObject.messageSpan.innerText, 'No'); 44 | expect(fixture.assertOnlyInstance.prompt.modal.visible, isFalse); 45 | }); 46 | 47 | test('modal disappears after clicking yes or no if we want it to', () async { 48 | await fixture.update( 49 | (PromptTestComponent cmp) => cmp.prompt.yesNoButtonsComponent.noButton.handleClick(MouseEvent('test'))); 50 | expect(fixture.assertOnlyInstance.prompt.modal.visible, isFalse); 51 | }); 52 | }); 53 | } 54 | 55 | @Component(selector: 'test', template: ''' 56 | 57 | 58 | ''', directives: [SkawaPromptComponent], providers: [popupBindings]) 59 | class PromptTestComponent { 60 | final String message = 'Should you?'; 61 | 62 | bool isVisible = true; 63 | 64 | @ViewChild('messageSpan') 65 | HtmlElement messageSpan; 66 | 67 | @ViewChild(SkawaPromptComponent) 68 | SkawaPromptComponent prompt; 69 | 70 | void changeText(String input) => (messageSpan as SpanElement).innerHtml = input; 71 | 72 | void yesCallback() { 73 | changeText('Yes'); 74 | isVisible = false; 75 | } 76 | 77 | void noCallback() { 78 | changeText('No'); 79 | isVisible = false; 80 | } 81 | } 82 | 83 | @PageObject() 84 | @CheckTag('test') 85 | abstract class TestPO { 86 | TestPO(); 87 | 88 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 89 | 90 | @root 91 | PageLoaderElement get rootElement; 92 | 93 | // PageLoaderElement get messageP => rootElement.getElementsByCss('message').first; 94 | 95 | @ByTagName('skawa-prompt') 96 | PromptPO get prompt; 97 | 98 | @ByTagName('span') 99 | PageLoaderElement get messageSpan; 100 | } 101 | 102 | @PageObject() 103 | abstract class PromptPO { 104 | PromptPO(); 105 | 106 | factory PromptPO.create(PageLoaderElement context) = $PromptPO.create; 107 | 108 | @ByClass('message') 109 | PageLoaderElement get messageP; 110 | 111 | @ByClass('btn-yes') 112 | PageLoaderElement get yesButton; 113 | 114 | @ByClass('btn-no') 115 | PageLoaderElement get noButton; 116 | } 117 | -------------------------------------------------------------------------------- /skawa_material_components/lib/snackbar/snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:angular/angular.dart'; 3 | import 'package:angular_components/material_button/material_button.dart'; 4 | import 'package:angular_components/utils/disposer/disposer.dart'; 5 | 6 | /// Snackbar service, emitting messages that the snackbar can listen to. 7 | /// You can emit messages with the showMessage function. The default display 8 | /// time of messages is 3 seconds, but you can specify any duration, and also callbacks. 9 | /// 10 | ///__Example__ 11 | /// 12 | /// _snackbarService.showMessage( 13 | /// 'Hello world', 14 | /// duration: new Duration(seconds: 2), 15 | /// action: new SnackAction()..label = 'call me back'..callback = callback); 16 | /// 17 | class SnackbarService { 18 | final StreamController _messageQueue = StreamController(); 19 | 20 | Stream get messages => _messageQueue.stream; 21 | 22 | void showMessage(String message, {Duration duration, SnackAction action}) { 23 | _messageQueue.add(SnackMessage() 24 | ..text = message 25 | ..duration = duration ?? _defaultDuration 26 | ..action = action); 27 | } 28 | 29 | static final Duration _defaultDuration = Duration(seconds: 3); 30 | } 31 | 32 | class SnackAction { 33 | String label; 34 | Function callback; 35 | } 36 | 37 | class SnackMessage { 38 | String text; 39 | Duration duration; 40 | SnackAction action; 41 | } 42 | 43 | /// A Snackbar component. See more at: https://material.io/guidelines/components/snackbars-toasts.html 44 | /// 45 | /// __Example__ 46 | /// 47 | /// 48 | /// 49 | /// Will display messages emitted by SnackbarService. Also a button if a SnackAction callback is specified. 50 | /// 51 | 52 | @Component( 53 | selector: 'skawa-snackbar', 54 | templateUrl: 'snackbar.html', 55 | styleUrls: ['snackbar.css'], 56 | directives: [MaterialButtonComponent, NgIf], 57 | changeDetection: ChangeDetectionStrategy.OnPush) 58 | class SkawaSnackbarComponent implements OnInit, OnDestroy { 59 | final ChangeDetectorRef _changeDetectorRef; 60 | final SnackbarService _snackbarService; 61 | final Disposer _tearDownDisposer = Disposer.oneShot(); 62 | 63 | SnackMessage message; 64 | SnackMessage nextMessage; 65 | Timer _messageTimer; 66 | bool show; 67 | Timer _animationBlocker; 68 | 69 | SkawaSnackbarComponent(this._changeDetectorRef, this._snackbarService); 70 | 71 | @override 72 | void ngOnInit() { 73 | final StreamSubscription subscription = _snackbarService.messages.listen((SnackMessage newMessage) { 74 | if (message == null) { 75 | message = newMessage; 76 | _slideIn(); 77 | } else { 78 | nextMessage = newMessage; 79 | _messageTimer?.cancel(); 80 | _messageTimer = null; 81 | if (_animationBlocker == null) { 82 | _slideOut(); 83 | } 84 | } 85 | }); 86 | _tearDownDisposer.addStreamSubscription(subscription); 87 | } 88 | 89 | void _slideIn() { 90 | show = true; 91 | _animationBlocker = Timer(minimumSlideInDelay, () { 92 | _animationBlocker = null; 93 | if (nextMessage != null) { 94 | _slideOut(); 95 | } 96 | }); 97 | _changeDetectorRef.markForCheck(); 98 | } 99 | 100 | void _slideOut() { 101 | show = false; 102 | _changeDetectorRef.markForCheck(); 103 | } 104 | 105 | void transitionEnd(_) { 106 | if (show) { 107 | _messageTimer = Timer(message.duration, () { 108 | _messageTimer = null; 109 | _slideOut(); 110 | }); 111 | } else { 112 | if (nextMessage == null) { 113 | message = null; 114 | _changeDetectorRef.markForCheck(); 115 | } else { 116 | message = nextMessage; 117 | nextMessage = null; 118 | _slideIn(); 119 | } 120 | } 121 | } 122 | 123 | @override 124 | void ngOnDestroy() => _tearDownDisposer.dispose(); 125 | 126 | static final Duration minimumSlideInDelay = Duration(milliseconds: 100); 127 | } 128 | -------------------------------------------------------------------------------- /skawa_components/test/infobar_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:angular/angular.dart'; 3 | import 'package:angular_test/angular_test.dart'; 4 | import 'package:pageloader/html.dart'; 5 | import 'package:skawa_components/infobar/infobar.dart'; 6 | import 'package:test/test.dart'; 7 | import 'infobar_test.template.dart' as ng; 8 | 9 | part 'infobar_test.g.dart'; 10 | 11 | void main() { 12 | ng.initReflector(); 13 | tearDown(disposeAnyRunningTest); 14 | final testBed = NgTestBed(); 15 | NgTestFixture fixture; 16 | TestPO pageObject; 17 | group('Infobar | ', () { 18 | setUp(() async { 19 | fixture = await testBed.create(); 20 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 21 | pageObject = TestPO.create(context); 22 | }); 23 | final String testLink = 'https://github.com/skawa-universe/skawa_components/'; 24 | final String materialIcon = 'code'; 25 | test('initialization with zero input', () async { 26 | expect(pageObject.trigger.innerText, 0.toString()); 27 | expect(pageObject.infobar.materialIcon.innerText, isEmpty); 28 | }); 29 | test('initialization with icon', () async { 30 | await fixture.update((testElement) => testElement.icon = materialIcon); 31 | expect(pageObject.trigger.innerText, 0.toString()); 32 | expect(pageObject.infobar.materialIcon.innerText, materialIcon); 33 | }); 34 | test('initialization with url', () async { 35 | await fixture.update((testElement) => testElement.url = testLink); 36 | expect(pageObject.trigger.innerText, 0.toString()); 37 | expect(pageObject.infobar.materialButton.attributes['title'], testLink); 38 | expect(pageObject.infobar.materialIcon.innerText, 'info'); 39 | }); 40 | test('initialization with url and url', () async { 41 | await fixture.update((testElement) { 42 | testElement.icon = materialIcon; 43 | testElement.url = testLink; 44 | }); 45 | expect(pageObject.trigger.innerText, 0.toString()); 46 | expect(pageObject.infobar.materialButton.attributes['title'], testLink); 47 | expect(pageObject.infobar.materialIcon.innerText, materialIcon); 48 | }); 49 | test('initialization with url then click 1X on the infobar button', () async { 50 | await fixture.update((testElement) => testElement.url = testLink); 51 | await pageObject.infobar.materialButton.click(); 52 | expect(pageObject.trigger.innerText, 1.toString()); 53 | expect(pageObject.infobar.materialButton.attributes['title'], testLink); 54 | expect(pageObject.infobar.materialIcon.innerText, 'info'); 55 | }); 56 | test('without url then click 3X on the infobar button', () async { 57 | await pageObject.infobar.materialButton.click(); 58 | await pageObject.infobar.materialButton.click(); 59 | await pageObject.infobar.materialButton.click(); 60 | expect(pageObject.trigger.innerText, 3.toString()); 61 | expect(pageObject.infobar.materialButton.attributes['title'], isNull); 62 | expect(pageObject.infobar.materialIcon.innerText, isEmpty); 63 | }); 64 | }); 65 | } 66 | 67 | @Component(selector: 'test', template: ''' 68 | 69 |
{{triggered}}
70 | ''', directives: [SkawaInfobarComponent]) 71 | class InfobarTestComponent { 72 | String icon; 73 | String url; 74 | int triggered = 0; 75 | 76 | void increment() => triggered = triggered + 1; 77 | } 78 | 79 | @PageObject() 80 | @CheckTag('test') 81 | abstract class TestPO { 82 | TestPO(); 83 | 84 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 85 | 86 | @ByTagName('skawa-infobar') 87 | InforbarPO get infobar; 88 | 89 | @ByTagName('[increment]') 90 | PageLoaderElement get trigger; 91 | } 92 | 93 | @PageObject() 94 | abstract class InforbarPO { 95 | InforbarPO(); 96 | 97 | factory InforbarPO.create(PageLoaderElement context) = $InforbarPO.create; 98 | 99 | @ByTagName('material-button') 100 | PageLoaderElement get materialButton; 101 | 102 | @ByTagName('material-icon') 103 | PageLoaderElement get materialIcon; 104 | } 105 | -------------------------------------------------------------------------------- /skawa_components/test/editor_render_target_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:pageloader/html.dart'; 3 | import 'package:skawa_components/markdown_editor/editor_render_target.dart'; 4 | import 'package:test/test.dart'; 5 | import 'package:angular/core.dart'; 6 | import 'package:angular_test/angular_test.dart'; 7 | import 'editor_artifacts.dart'; 8 | import 'editor_render_target_test.template.dart' as ng; 9 | 10 | part 'editor_render_target_test.g.dart'; 11 | 12 | @GenerateInjector([ClassProvider(EditorRenderer, useClass: MockRenderer)]) 13 | final InjectorFactory rootInjector = ng.rootInjector$Injector; 14 | 15 | void main() { 16 | ng.initReflector(); 17 | tearDown(disposeAnyRunningTest); 18 | group('EditorRenderTarget | ', () { 19 | test('can be edited displays data', () async { 20 | final fixture = await NgTestBed(rootInjector: rootInjector).create(); 21 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 22 | final pageObject = TestPO.create(context); 23 | await pageObject.counter.click(); 24 | expect(pageObject.renderTarget.innerText, isEmpty); 25 | }); 26 | test('can be edited displays data', () async { 27 | final fixture = await NgTestBed(rootInjector: rootInjector) 28 | .create(beforeChangeDetection: (testElement) => testElement.content = '
Cat Lion
'); 29 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 30 | final pageObject = TestPO.create(context); 31 | await pageObject.counter.click(); 32 | expect(pageObject.renderTarget.innerText, 'Cat Lion'); 33 | await fixture.update(); 34 | expect(fixture.assertOnlyInstance.renderedContent, '
Cat Lion
'); 35 | fixture.assertOnlyInstance.editorRenderTarget.htmlElement.children 36 | .forEach((child) => expect(child.classes, isEmpty)); 37 | }); 38 | test('can be edited displays data', () async { 39 | final fixture = await NgTestBed().create(beforeChangeDetection: (testElement) { 40 | testElement.content = '
Cat Lion
'; 41 | }); 42 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 43 | final pageObject = TestPO.create(context); 44 | await fixture.update((testElement) { 45 | testElement.content = ''' 46 |
    47 |
  • Cat
  • 48 |
  • Dog
  • 49 |
50 | '''; 51 | testElement.cssClasses = ['cat', 'lion']; 52 | }); 53 | await pageObject.counter.click(); 54 | await fixture.update(); 55 | expect( 56 | fixture.assertOnlyInstance.renderedContent, 57 | '
    \n' 58 | '
  • Cat
  • \n' 59 | '
  • Dog
  • \n' 60 | '
\n' 61 | ' '); 62 | expect( 63 | pageObject.renderTarget.innerText, 64 | 'Cat\n' 65 | ' Dog'); 66 | fixture.assertOnlyInstance.editorRenderTarget.htmlElement.children.forEach((child) { 67 | expect(child.classes.contains('cat'), isTrue); 68 | expect(child.classes.contains('lion'), isTrue); 69 | }); 70 | }); 71 | }); 72 | } 73 | 74 | @Component(selector: 'test', template: ''' 75 |
76 |
{{renderedContent}}
77 | ''', directives: [EditorRenderTarget], providers: [ClassProvider(EditorRenderer, useClass: HtmlRenderer)]) 78 | class TestComponent { 79 | String content; 80 | String renderedContent; 81 | List cssClasses = []; 82 | 83 | void counter(String newTarget) => renderedContent = newTarget; 84 | 85 | void update() => editorRenderTarget.updateRender(content, classes: cssClasses); 86 | 87 | @ViewChild(EditorRenderTarget) 88 | EditorRenderTarget editorRenderTarget; 89 | } 90 | 91 | @PageObject() 92 | @CheckTag('test') 93 | abstract class TestPO { 94 | TestPO(); 95 | 96 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 97 | 98 | @ByCss('[editorRenderTarget]') 99 | PageLoaderElement get renderTarget; 100 | 101 | @ByClass('counter') 102 | PageLoaderElement get counter; 103 | } 104 | -------------------------------------------------------------------------------- /skawa_material_components/lib/base_implementations/grid/grid.dart: -------------------------------------------------------------------------------- 1 | library skawa_component_collection.grid; 2 | 3 | import 'dart:html'; 4 | import 'dart:math' show max; 5 | 6 | /// Returns a set of updates to be performed on the DOM 7 | /// to achieve the desired arrangement for the grid 8 | class GridUpdate { 9 | final List> tilePositions; 10 | final int gridHeight; 11 | 12 | GridUpdate(this.tilePositions, this.gridHeight); 13 | } 14 | 15 | /// Represents a tile in the Grid 16 | abstract class GridTile { 17 | factory GridTile(Element gridTile) { 18 | return _DomGridTile(gridTile); 19 | } 20 | 21 | /// Width of the grid tile 22 | int get width; 23 | 24 | /// Height of grid tile 25 | int get height; 26 | 27 | /// Repositions the tile to [pos] 28 | void reposition(Point pos); 29 | } 30 | 31 | abstract class DomTransformReposition { 32 | Element get tile; 33 | 34 | int get width => tile.clientWidth; 35 | 36 | int get height => tile.clientHeight; 37 | 38 | void reposition(Point pos) => tile.style.transform = 'translate(${pos.x}px, ${pos.y}px)'; 39 | } 40 | 41 | abstract class GridBase implements Grid { 42 | /// Calculates the positions for [tiles] in a grid with width of [gridWidth] 43 | /// 44 | /// Default gutter size (spacing between tiles) is 16px 45 | @override 46 | GridUpdate calculateGridUpdate(int gridWidth, {int gutterSize = 16}) { 47 | final int tileWidth = tiles.first.width; 48 | final int tileWidthWithGutter = (tileWidth + gutterSize); 49 | final int colNumber = gridWidth ~/ tileWidthWithGutter == 0 ? 1 : gridWidth ~/ tileWidthWithGutter; 50 | final int xAdjustmentForCentering = (gridWidth - tileWidthWithGutter * colNumber - gutterSize) ~/ 2; 51 | final List xTranslations = 52 | List.generate(colNumber, (int index) => index * tileWidthWithGutter + xAdjustmentForCentering, growable: false); 53 | final List yTranslationForCol = List.filled(colNumber, 0, growable: false); 54 | final List> tileTransformations = List>(tiles.length); 55 | 56 | int colIndex = 0; 57 | for (int tileNumber = 0; tileNumber < tiles.length; ++tileNumber) { 58 | GridTile tile = tiles.elementAt(tileNumber); 59 | int maxYTranslation = yTranslationForCol.reduce(max); 60 | int maxColIndex = yTranslationForCol.indexOf(maxYTranslation); 61 | bool multiple = yTranslationForCol.where((e) => e == maxYTranslation).length > 1; 62 | if (!multiple && maxColIndex == colIndex) { 63 | colIndex = (colIndex + 1) % colNumber; 64 | } 65 | tileTransformations[tileNumber] = Point(xTranslations[colIndex], yTranslationForCol[colIndex]); 66 | int tileBottom = tile.height + yTranslationForCol[colIndex] + gutterSize; 67 | yTranslationForCol[colIndex] = tileBottom; 68 | colIndex = (colIndex + 1) % colNumber; 69 | } 70 | 71 | int newGridHeight = yTranslationForCol.reduce(max) + gutterSize; 72 | return GridUpdate(tileTransformations, newGridHeight); 73 | } 74 | } 75 | 76 | abstract class Grid { 77 | factory Grid(Element grid, List tiles) { 78 | return _DomGrid(grid, tiles); 79 | } 80 | 81 | Iterable get tiles; 82 | 83 | set visible(bool val); 84 | 85 | bool get visible; 86 | 87 | GridUpdate calculateGridUpdate(int gridWidth, {int gutterSize = 16}); 88 | 89 | /// Updates the grid 90 | void updateAndDisplay(bool forceRefresh); 91 | } 92 | 93 | class _DomGridTile implements GridTile { 94 | final Element _tile; 95 | 96 | _DomGridTile(this._tile); 97 | 98 | @override 99 | int get width => _tile.clientWidth; 100 | 101 | @override 102 | int get height => _tile.clientHeight; 103 | 104 | @override 105 | void reposition(Point pos) => _tile.style.transform = 'translate(${pos.x}px, ${pos.y}px)'; 106 | } 107 | 108 | class _DomGrid extends GridBase { 109 | @override 110 | final List tiles; 111 | final Element _grid; 112 | 113 | _DomGrid(this._grid, this.tiles); 114 | 115 | @override 116 | void updateAndDisplay(bool forceRefresh, [_]) { 117 | GridUpdate gridUpdate = calculateGridUpdate(_grid.clientWidth); 118 | visible = true; 119 | _grid.style..height = '${gridUpdate.gridHeight}px'; 120 | for (int i = 0; i < gridUpdate.tilePositions.length; ++i) { 121 | Point newPosition = gridUpdate.tilePositions[i]; 122 | tiles[i].reposition(newPosition); 123 | } 124 | } 125 | 126 | @override 127 | bool get visible => _grid.style.visibility != 'hidden'; 128 | 129 | @override 130 | set visible(bool val) => _grid.style.visibility = val ? '' : 'hidden'; 131 | } 132 | -------------------------------------------------------------------------------- /skawa_components/lib/code_mirror/code_mirror.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:html'; 3 | import 'dart:js'; 4 | import 'dart:js_util'; 5 | import 'package:angular/angular.dart'; 6 | import 'package:angular_components/material_icon/material_icon.dart'; 7 | 8 | import 'code_mirror_config.dart'; 9 | import 'code_mirror_interop.dart' as js_ck; 10 | import 'code_mirror_mode.dart'; 11 | 12 | @Component( 13 | selector: 'skawa-code-mirror', 14 | templateUrl: 'code_mirror.html', 15 | styleUrls: ['code_mirror.css'], 16 | directives: [NgIf, MaterialIconComponent], 17 | changeDetection: ChangeDetectionStrategy.OnPush) 18 | class CodeMirrorComponent implements OnDestroy { 19 | final StreamController _changeController = StreamController.broadcast(); 20 | final ChangeDetectorRef changeDetectorRef; 21 | 22 | js_ck.CodeMirror _codeMirror; 23 | String _value = ''; 24 | CodeMirrorMode _mode; 25 | List errorList = []; 26 | List warningList = []; 27 | 28 | @ViewChild('textarea') 29 | HtmlElement textarea; 30 | 31 | @Input() 32 | bool withLint = true; 33 | 34 | CodeMirrorComponent(this.changeDetectorRef); 35 | 36 | bool get hasError => errorList.any((error) => error.severity == CodeMirrorError.error); 37 | 38 | @Input() 39 | set mode(CodeMirrorMode mode) { 40 | if (_mode != mode) { 41 | _codeMirror?.off('change', _onChange(_mode)); 42 | _codeMirror?.toTextArea(); 43 | load(mode); 44 | } else { 45 | _mode = mode; 46 | } 47 | } 48 | 49 | @Input() 50 | set value(String value) { 51 | String validValue = value ?? ''; 52 | if (validValue != _value && _codeMirror != null) { 53 | _codeMirror.setValue(validValue); 54 | _lint(_mode); 55 | } 56 | _value = validValue; 57 | } 58 | 59 | String get value => _value; 60 | 61 | @Output('change') 62 | Stream get onChange => _changeController.stream; 63 | 64 | void load(CodeMirrorMode mode) { 65 | _codeMirror = js_ck.CodeMirror.fromTextArea( 66 | textarea, 67 | CodeMirrorConfig( 68 | mode: mode.modeName ?? mode.mode, 69 | theme: mode.theme, 70 | extraKeys: {'Ctrl-Space': allowInterop(_showHint), 'Cmd-Space': allowInterop(_showHint)}, 71 | gutters: ["CodeMirror-lint-markers"]).toJsConfig); 72 | _codeMirror.setValue(_value); 73 | _lint(mode); 74 | _codeMirror.on('change', _onChange(mode)); 75 | _mode = mode; 76 | } 77 | 78 | Function _onChange(CodeMirrorMode mode) => allowInterop((_, __) { 79 | _value = _codeMirror.getValue(); 80 | _lint(mode); 81 | }); 82 | 83 | void _showHint(_) => _codeMirror.showHint(_codeMirror, js_ck.CodeMirrorHint.jsHint); 84 | 85 | void _lint(CodeMirrorMode mode) { 86 | if (withLint) { 87 | warningList = []; 88 | errorList = []; 89 | if (mode.linter != null) { 90 | context['CodeMirror']['lint'] 91 | .callMethod(mode.linter, [ 92 | _value, 93 | jsify({"indent": 1}) 94 | ]) 95 | ?.map((Object object) => CodeMirrorError.fromObject(object as JsObject)) 96 | ?.forEach((CodeMirrorError error) { 97 | if (error.severity == CodeMirrorError.error) { 98 | errorList.add(error); 99 | } else if (error.severity == CodeMirrorError.warning) { 100 | warningList.add(error); 101 | } 102 | }); 103 | } 104 | } 105 | _changeController.add(_value); 106 | changeDetectorRef.markForCheck(); 107 | } 108 | 109 | @override 110 | void ngOnDestroy() { 111 | _changeController.close(); 112 | _codeMirror?.toTextArea(); 113 | } 114 | } 115 | 116 | class CodeMirrorError { 117 | final String message; 118 | final String severity; 119 | final CodeMirrorPosition from; 120 | final CodeMirrorPosition to; 121 | 122 | CodeMirrorError.fromObject(JsObject object) 123 | : this.message = object['message'] as String, 124 | this.severity = object['severity'] as String, 125 | this.from = CodeMirrorPosition.fromJsObject(object['from'] as JsObject), 126 | this.to = CodeMirrorPosition.fromJsObject(object['to'] as JsObject); 127 | 128 | static const String error = 'error'; 129 | static const String warning = 'warning'; 130 | } 131 | 132 | class CodeMirrorPosition { 133 | final double line; 134 | final double ch; 135 | 136 | CodeMirrorPosition.fromJsObject(JsObject object) 137 | : this.line = object['line'] as double, 138 | this.ch = object['ch'] as double; 139 | } 140 | -------------------------------------------------------------------------------- /skawa_material_components/lib/card/card.dart: -------------------------------------------------------------------------------- 1 | import 'package:angular/core.dart'; 2 | 3 | import '../util/attribute.dart' as attr_util; 4 | import 'card_actions.dart'; 5 | import 'card_directives.dart'; 6 | 7 | export 'card.dart'; 8 | export 'card_actions.dart'; 9 | export 'card_directives.dart'; 10 | 11 | const List skawaCardDirectives = [ 12 | SkawaCardHeaderComponent, 13 | SkawaCardHeaderSubheadDirective, 14 | SkawaCardHeaderTitleDirective, 15 | SkawaCardHeaderImageDirective, 16 | SkawaCardContentComponent, 17 | SkawaCardActionsComponent, 18 | SkawaCardComponent 19 | ]; 20 | 21 | /// A card component. [See more at](https://material.io/guidelines/components/cards.html) 22 | /// 23 | /// __Example:__ 24 | /// 25 | /// 26 | /// Some content. 27 | /// 28 | /// 29 | /// __Inputs__: 30 | /// - `no-shadow`: Will not add default shadow. 31 | /// 32 | @Component( 33 | selector: 'skawa-card', 34 | template: '', 35 | styleUrls: ['card.css'], 36 | changeDetection: ChangeDetectionStrategy.OnPush, 37 | visibility: Visibility.all) 38 | class SkawaCardComponent { 39 | @ContentChild(SkawaCardHeaderComponent) 40 | SkawaCardHeaderComponent cardHeader; 41 | 42 | @ContentChild(SkawaCardContentComponent) 43 | SkawaCardContentComponent cardContent; 44 | 45 | bool get hasHeader => cardHeader != null; 46 | } 47 | 48 | /// Content area for cards 49 | /// 50 | /// __Example:__ 51 | /// 52 | /// 53 | /// Some content that is collapsed by default. 54 | /// 55 | /// 56 | /// __Inputs:__ 57 | /// - `collapsed: bool` -- Initial state of the component will be collapsed. 58 | /// 59 | /// __Properties:__ 60 | /// 61 | /// - `fullWidth` -- removes padding for card content 62 | /// 63 | @Component( 64 | selector: 'skawa-card-content', 65 | exportAs: 'cardcontent', 66 | template: '', 67 | styleUrls: ['card_content.css'], 68 | changeDetection: ChangeDetectionStrategy.OnPush) 69 | class SkawaCardContentComponent { 70 | final SkawaCardComponent parentCard; 71 | 72 | SkawaCardContentComponent(this.parentCard); 73 | 74 | @HostBinding('class.with-header') 75 | bool get withHeader => parentCard.hasHeader; 76 | 77 | @Input() 78 | bool collapsed; 79 | 80 | @HostBinding('class.skawa-collapsed') 81 | bool get isCollapsed => attr_util.isPresent(collapsed); 82 | 83 | /// Toggle collapsed state content area 84 | void toggle() { 85 | collapsed = attr_util.toggleAttribute(collapsed); 86 | } 87 | } 88 | 89 | /// Header component for a [SkawaCardComponent]. 90 | /// 91 | /// __Example:__ 92 | /// 93 | /// 94 | /// 95 | /// 96 | /// 97 | /// 98 | /// __Inputs:__ 99 | /// - `statusColor: String` -- The color of the box-shadow-top. Accept only rgb() or rgba() formats. Defaults to transparent. 100 | /// 101 | @Component( 102 | selector: 'skawa-card-header', 103 | template: '', 104 | styleUrls: ['card_header.css'], 105 | changeDetection: ChangeDetectionStrategy.OnPush) 106 | class SkawaCardHeaderComponent { 107 | @ContentChild(SkawaCardHeaderTitleDirective) 108 | SkawaCardHeaderTitleDirective title; 109 | 110 | @ContentChild(SkawaCardHeaderSubheadDirective) 111 | SkawaCardHeaderSubheadDirective subhead; 112 | 113 | @ContentChild(SkawaCardHeaderImageDirective) 114 | SkawaCardHeaderImageDirective image; 115 | 116 | @ContentChild(SkawaCardActionsComponent) 117 | SkawaCardActionsComponent headerActions; 118 | 119 | @HostBinding('class.with-title-image') 120 | bool get withTitleImage => image != null; 121 | 122 | @HostBinding('class.with-subhead') 123 | bool get withSubHead => subhead != null; 124 | 125 | @HostBinding('class.with-actions') 126 | bool get hasActions => headerActions != null; 127 | 128 | /// Status color for the card 129 | /// 130 | /// Must be provided in rgb() or rgba() format, hex values are 131 | /// not picked up. 132 | @Input() 133 | String statusColor = 'transparent'; 134 | 135 | @HostBinding('style.box-shadow') 136 | String get statusStyle { 137 | // if set to null remove styling 138 | if (statusColor == null) { 139 | return null; 140 | } 141 | // if matches rgb() or rgba() format, use it 142 | if (_rgbaRegexp.hasMatch(statusColor)) { 143 | return 'inset 0 4px 0 0 $statusColor'; 144 | } 145 | return null; 146 | } 147 | 148 | static final RegExp _rgbaRegexp = RegExp(r'rgba?\s*\((?:\d+(?:\.[\d]+)?,?\s*){3,4}\)'); 149 | } 150 | -------------------------------------------------------------------------------- /skawa_material_components/test/snackbar_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'dart:async'; 3 | import 'package:angular/angular.dart'; 4 | import 'package:angular_test/angular_test.dart'; 5 | import 'package:skawa_material_components/snackbar/snackbar.dart'; 6 | import 'package:test/test.dart'; 7 | import 'package:pageloader/html.dart'; 8 | 9 | import 'snackbar_test.template.dart' as ng; 10 | import 'snackbar_test_po.dart'; 11 | 12 | @GenerateInjector([ClassProvider(SnackbarService)]) 13 | final InjectorFactory rootInjector = ng.rootInjector$Injector; 14 | 15 | void main() { 16 | ng.initReflector(); 17 | tearDown(disposeAnyRunningTest); 18 | final testBed = NgTestBed(rootInjector: rootInjector); 19 | NgTestFixture fixture; 20 | TestPO pageObject; 21 | group('Snackbar |', () { 22 | setUp(() async { 23 | fixture = await testBed.create(); 24 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 25 | pageObject = TestPO.create(context); 26 | }); 27 | test('initializing', () async => expect(pageObject.snackbar, isNotNull)); 28 | test('displays a message', () async { 29 | await fixture.update((testElement) => testElement.callbackString = 'hello'); 30 | await pageObject.messageSpan.click(); 31 | await Future.delayed(SkawaSnackbarComponent.minimumSlideInDelay); 32 | expect(pageObject.snackbar.rootElement.displayed, isTrue); 33 | expect(pageObject.snackbar.messageContainer.visibleText, 'hello'); 34 | expect(pageObject.snackbar.rootElement.classes.contains('show'), isTrue); 35 | }); 36 | test('slides out after default duration (3 seconds)', () async { 37 | await pageObject.messageSpan.click(); 38 | await Future.delayed(Duration(seconds: 5), () => null); 39 | expect(pageObject.snackbar.rootElement.classes.contains('show'), isFalse); 40 | }, skip: "This test is flaky currently"); 41 | test('slides out after specified duration(2 seconds)', () async { 42 | await fixture.update((testElement) { 43 | testElement 44 | ..callbackString = 'hello' 45 | ..callbackDuration = (Duration(seconds: 2)); 46 | }); 47 | await pageObject.messageSpan.click(); 48 | await Future.delayed(Duration(seconds: 3), () => null); 49 | expect(pageObject.snackbar.rootElement.classes.contains('show'), isFalse); 50 | }); 51 | test('displays button if callback is specified', () async { 52 | await fixture.update((testElement) async { 53 | testElement 54 | ..callbackString = 'call me back' 55 | ..callbackFunction = (() => print('call me back')); 56 | }); 57 | await pageObject.callbackP.click(); 58 | await Future.delayed(SkawaSnackbarComponent.minimumSlideInDelay); 59 | PageLoaderElement button = pageObject.snackbar.materialButton; 60 | expect(button, isNotNull); 61 | expect(button.visibleText, 'call me back'); 62 | }); 63 | test('callback on button click', () async { 64 | await fixture.update((testElement) async { 65 | testElement 66 | ..callbackString = 'call me back' 67 | ..callbackFunction = (() => testElement.callbackValue = 'callback done'); 68 | }); 69 | await pageObject.callbackP.click(); 70 | await Future.delayed(SkawaSnackbarComponent.minimumSlideInDelay); 71 | await pageObject.snackbar.materialButton.click(); 72 | expect(pageObject.callbackP.innerText, contains('callback done')); 73 | }); 74 | }); 75 | } 76 | 77 | @Component(selector: 'test', template: ''' 78 | 79 | 80 |

81 | {{callbackValue}} 82 |

83 | ''', directives: [SkawaSnackbarComponent]) 84 | class SnackbarTestComponent { 85 | final SnackbarService _snackbarService; 86 | final ChangeDetectorRef cd; 87 | 88 | String callbackValue; 89 | String callbackString; 90 | Duration callbackDuration; 91 | Function callbackFunction; 92 | 93 | @ViewChild(SkawaSnackbarComponent) 94 | SkawaSnackbarComponent snackbar; 95 | 96 | SnackbarTestComponent(this._snackbarService, this.cd); 97 | 98 | void showMessage(String message, [Duration duration]) { 99 | _snackbarService.showMessage(message, duration: duration); 100 | } 101 | 102 | void showMessageWithCallback(String message, [Duration duration, Function callback]) { 103 | SnackAction action = SnackAction() 104 | ..label = 'call me back' 105 | ..callback = callback; 106 | _snackbarService.showMessage(message, duration: duration, action: action); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /skawa_components/test/markdown_editor_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'dart:async'; 3 | import 'package:angular/angular.dart'; 4 | import 'package:angular_components/laminate/popup/module.dart'; 5 | import 'package:angular_test/angular_test.dart'; 6 | import 'package:pageloader/html.dart'; 7 | import 'package:skawa_components/markdown_editor/editor_render_target.dart'; 8 | import 'package:skawa_components/markdown_editor/markdown_editor.dart'; 9 | import 'package:test/test.dart'; 10 | import 'markdown_editor_test.template.dart' as ng; 11 | 12 | part 'markdown_editor_test.g.dart'; 13 | 14 | void main() { 15 | ng.initReflector(); 16 | tearDown(disposeAnyRunningTest); 17 | group('Markdown Editor', () { 18 | tearDown(disposeAnyRunningTest); 19 | final String input = "cica"; 20 | test('can be edited displays data', () async { 21 | final fixture = await NgTestBed().create(); 22 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 23 | final markdownEditorPage = MarkdownEditorPage.create(context); 24 | await markdownEditorPage.editMarkdown(); 25 | TextAreaElement textarea = markdownEditorPage.textarea; 26 | await Future.delayed(emmitDelay); 27 | expect(markdownEditorPage.markdownContainerDiv.displayed, isFalse); 28 | expect(textarea.displayed, isTrue); 29 | expect(textarea.rootElement.properties['value'], initialValue); 30 | }); 31 | test('can be edited and type', () async { 32 | final fixture = await NgTestBed().create(); 33 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 34 | final markdownEditorPage = MarkdownEditorPage.create(context); 35 | await markdownEditorPage.editMarkdown(); 36 | TextAreaElement textarea = markdownEditorPage.textarea; 37 | await textarea.typing(input); 38 | await Future.delayed(emmitDelay); 39 | expect(markdownEditorPage.markdownContainerDiv.displayed, isFalse); 40 | expect(textarea.displayed, isTrue); 41 | expect(textarea.rootElement.properties['value'], '$initialValue$input'); 42 | }); 43 | test('can be edited and preview', () async { 44 | final fixture = await NgTestBed().create(); 45 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 46 | final markdownEditorPage = MarkdownEditorPage.create(context); 47 | await markdownEditorPage.editMarkdown(); 48 | TextAreaElement textarea = markdownEditorPage.textarea; 49 | await textarea.typing(input); 50 | await Future.delayed(emmitDelay); 51 | await markdownEditorPage.buttons[2].click(); 52 | // expect(markdownEditorPage.markdownContainerDiv.displayed, isTrue); 53 | expect(markdownEditorPage.markdownContainerDiv.innerText, '$initialValue$input'); 54 | }); 55 | }); 56 | } 57 | 58 | @PageObject() 59 | @CheckTag('markdown-test') 60 | abstract class MarkdownEditorPage { 61 | MarkdownEditorPage(); 62 | 63 | factory MarkdownEditorPage.create(PageLoaderElement context) = $MarkdownEditorPage.create; 64 | 65 | @First(ByClass('markdown-container')) 66 | PageLoaderElement get markdownContainerDiv; 67 | 68 | @ByTagName('textarea') 69 | TextAreaElement get textarea; 70 | 71 | @ByTagName('button') 72 | List get buttons; 73 | 74 | Future editMarkdown() async => markdownContainerDiv.click(); 75 | } 76 | 77 | @PageObject() 78 | abstract class TextAreaElement { 79 | TextAreaElement(); 80 | 81 | factory TextAreaElement.create(PageLoaderElement context) = $TextAreaElement.create; 82 | 83 | @root 84 | PageLoaderElement get rootElement; 85 | 86 | String get innerText => rootElement.innerText; 87 | 88 | bool get displayed => rootElement.displayed; 89 | 90 | Future typing(String input) async => await rootElement.type(input); 91 | } 92 | 93 | @Component(selector: 'markdown-test', template: ''' 94 | 95 |
Nothing to show you yet
96 |
97 | 98 | 99 | 100 | 101 | ''', directives: [ 102 | SkawaMarkdownEditorComponent 103 | ], providers: [ 104 | popupDebugBindings, 105 | Provider(EditorRenderer, useClass: MarkdownRenderer), 106 | ValueProvider(Duration, updateDelay) 107 | ], exports: [ 108 | initialValue 109 | ]) 110 | class AppComponent { 111 | @ViewChild(SkawaMarkdownEditorComponent) 112 | SkawaMarkdownEditorComponent markdownEditorComponent; 113 | } 114 | 115 | const int updateDelayMS = 9; 116 | const Duration updateDelay = const Duration(milliseconds: updateDelayMS); 117 | const Duration emmitDelay = const Duration(milliseconds: updateDelayMS + 2); 118 | const String initialValue = "hello"; 119 | -------------------------------------------------------------------------------- /skawa_components/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.5.3 2 | 3 | - Fixes CodeMirrorComponent component loading for javascript editor 4 | 5 | ## 1.5.2 6 | 7 | - Fixes CodeMirrorComponent component in case of minified scripts 8 | 9 | ## 1.5.1 10 | 11 | - Made CKEditor change event working 12 | 13 | ## 1.5.0 14 | 15 | - angular updated to the stable 6.0.0 version 16 | 17 | ## 1.4.0 18 | 19 | - New structural directives: `[featureEnabled]` and `[featureDisabled]` 20 | 21 | ## 1.3.3 22 | 23 | - patch version for 6.0.0-alpha 24 | 25 | ## 1.3.2 26 | 27 | - CKEditor can be used with custom config map 28 | 29 | ## 1.3.1 30 | 31 | - patch version for 6.0.0-alpha 32 | 33 | ## 1.3.0 34 | 35 | - From this minor version, there will be a path version for the angular alpha version too 36 | - Performance update on SkawaListWrapperComponent for normal use cases 37 | 38 | ## 1.2.2 39 | 40 | - SkawaForDirective onLoad ouput stream added 41 | - CodeMirrorModeWithLink static constants fixed 42 | 43 | ## 1.2.1 44 | 45 | - added support for Dart 2.5 46 | - CodeMirrorComponent, DiffComponent and SkawaListWrapperComponent added 47 | 48 | ## 1.1.0 49 | 50 | - Pub constraints updated 51 | 52 | ## 1.0.0+4 53 | 54 | - Pub constraints updated 55 | 56 | ## 1.0.0+3 57 | 58 | - Fixed SkawaMarkdownEditorComponent 59 | 60 | ## 1.0.0+2 61 | 62 | - Fixed EditorRenderSource 63 | 64 | ## 1.0.0+1 65 | 66 | - Fixed missing pub constraints 67 | 68 | ## 1.0.0 69 | 70 | - Release to pub 71 | 72 | ## 1.0.0-beta 73 | 74 | - Splitted the original package, removed the material components from this package 75 | - SkawaRandomColorizePipe renamed to SkawaHexColorizePipe 76 | - SkawaAppbarComponent, SkawaDrawerComponent, SkawaSidebarComponent became deprecated 77 | 78 | ## 1.0.0-alpha+7 79 | 80 | - SkawaRawMarkdownRendererComponent added 81 | 82 | ## 1.0.0-alpha+6 83 | 84 | - SkawaMarkdownEditorComponent disable ex improved 85 | 86 | ## 1.0.0-alpha+5 87 | 88 | - SkawaMarkdownEditorComponent can be disabled 89 | 90 | ## 1.0.0-alpha+4 91 | 92 | - Added a new output stream to SkawaMarkdownEditorComponent which emits on displayMode change 93 | - SkawaMarkdownRendererComponent added 94 | - SkawaDataTableComponent non-highlightable mode fixed 95 | 96 | ## 1.0.0-alpha+3 97 | 98 | - SkawaInfobarComponent button open the url in a new tab 99 | - SkawaDataTableComponent can be non highlightable 100 | 101 | ## 1.0.0-alpha+2 102 | 103 | - SkawaMarkdownEditorComponent now updates EditorRenderTarget with the proper classes 104 | 105 | ## 1.0.0-alpha+1 106 | 107 | - fix minor issue in toggleAttribute method 108 | - snackbar_test refactored 109 | - added flaky-on-travis tags to tests which is never fails on localhost but sometimes fails on travis 110 | - moved PromptComponent to lib folder due to Angular4 migration 111 | 112 | ## 1.0.0-alpha 113 | - Upgrade to Angular4 114 | 115 | ## 0.0.16 116 | - Added new component 117 | 118 | ## 0.0.15 119 | 120 | - Added new component `` and corresponding Directives. 121 | - Ckeditor description corrected 122 | - analysis_options became stricter 123 | - travis script now running dartfmt, if the code is not formated properly then exit 124 | 125 | ## 0.0.14 126 | - Added tests to snackbar 127 | - Changed angular2 version from ^3.1.0 to 3.1.0 128 | 129 | ## 0.0.13 130 | - Added SkawaRandomColorizePipe 131 | 132 | ## 0.0.12 133 | - Added sort logic to SkawaDataTableComponent 134 | 135 | ## 0.0.11 136 | - Modified snackbar to use material popup. 137 | - Snackbar should now be in the bottom left corner of the screen always. 138 | - Changed angular_components version from "^0.5.1" to "^0.5.3+1" 139 | 140 | ## 0.0.10 141 | 142 | - DataTable highlight event won't be triggered when selector checkbox is clicked 143 | 144 | ## 0.0.9 145 | 146 | - Changed to `DataTable` 147 | - introduction of the concept of `primaryAction`. Columns with accessors can subscript to `(trigger)` 148 | action. 149 | - rows can be `(highlight)`-ed without making a change in selection 150 | 151 | ## 0.0.8+1 152 | 153 | - Fixed a strong-mode error about missing trigger on Snackbar 154 | - Changed `DataTableAccessor` return value from `dynamic` to `String` 155 | 156 | ## 0.0.8 157 | 158 | - Add `SnackbarComponent` 159 | 160 | ## 0.0.7 161 | 162 | - Add some new functionality to ``: 163 | - `change` event is emitted when selection changes 164 | - setting `multiSelection` toggles whether only a single or multiple elements can be selected 165 | 166 | ## 0.0.6+1 167 | 168 | - fixed an issue where dart2js would warn about `??` in for loop 169 | 170 | ## 0.0.6 171 | 172 | - Added support for column renderer for `` 173 | 174 | ## 0.0.5 175 | 176 | - Added new component `` 177 | 178 | ## 0.0.4 179 | 180 | - CKEditor accepts initial value as `content` input property 181 | 182 | ## 0.0.3+1 183 | 184 | - Fixed transformer usage 185 | 186 | ## 0.0.3 187 | 188 | - Added new component `` 189 | 190 | ## 0.0.2 191 | 192 | - Added tests with travis integration 193 | - Minor changes to components and their styling -------------------------------------------------------------------------------- /skawa_material_components/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.5.1 2 | 3 | - Added disabled field to the RowData classes 4 | 5 | 6 | ## 1.5.0 7 | 8 | - angular updated to the stable 6.0.0 version` 9 | 10 | ## 1.3.3 11 | 12 | - patch version for 6.0.0-alpha 13 | 14 | ## 1.3.2 15 | 16 | - SkawaMaterialBannerComponent renamed to SkawaBannerComponent, added missing styling 17 | 18 | ## 1.3.1 19 | 20 | - patch version for 6.0.0-alpha 21 | 22 | ## 1.3.0 23 | 24 | - From this minor version, there will be a path version for the angular alpha version too 25 | - SkawaMaterialBannerComponent added 26 | 27 | 28 | ## 1.2.2 29 | 30 | - Minor styling on SkawaDataTableComponent 31 | 32 | ## 1.2.1 33 | 34 | - added support for Dart 2.5 35 | - CardOverflowDirective added 36 | 37 | ## 1.2.0 38 | 39 | - added support for the dart 2.4 and angular 6.0.0-alpha 40 | - `titleAccessor` added to SkawaDataTableColComponent 41 | 42 | ## 1.1.0+1 43 | 44 | - Fix: SkawaDataTableComponent selectable field has a default value, to evade errors after 2.3 dart version 45 | 46 | ## 1.1.0 47 | 48 | - Pub constraints updated 49 | 50 | ## 1.0.0+4 51 | 52 | - SkawaDataTableComponent columns can be updated at runtime, and coloring won't be messed up after sorting 53 | 54 | ## 1.0.0+3 55 | 56 | - Pub constraints updated 57 | - ExtendedMaterialIconComponent added 58 | 59 | ## 1.0.0+2 60 | 61 | - SkawaDataTableComponent tests using components with generic parameter 62 | 63 | ## 1.0.0+1 64 | 65 | - Fixed missing pub constraints 66 | 67 | ## 1.0.0 68 | 69 | - Release to pub 70 | 71 | ## 1.0.0-beta 72 | 73 | - Splitted the original package, removed the non-material components from this package 74 | - RowData now has additional classes filed to be able style them differently 75 | 76 | ## 1.0.0-alpha+7 77 | 78 | - SkawaRawMarkdownRendererComponent added 79 | 80 | ## 1.0.0-alpha+6 81 | 82 | - SkawaMarkdownEditorComponent disable ex improved 83 | 84 | ## 1.0.0-alpha+5 85 | 86 | - SkawaMarkdownEditorComponent can be disabled 87 | 88 | ## 1.0.0-alpha+4 89 | 90 | - Added a new output stream to SkawaMarkdownEditorComponent which emits on displayMode change 91 | - SkawaMarkdownRendererComponent added 92 | - SkawaDataTableComponent non-highlightable mode fixed 93 | 94 | ## 1.0.0-alpha+3 95 | 96 | - SkawaInfobarComponent button open the url in a new tab 97 | - SkawaDataTableComponent can be non highlightable 98 | 99 | ## 1.0.0-alpha+2 100 | 101 | - SkawaMarkdownEditorComponent now updates EditorRenderTarget with the proper classes 102 | 103 | ## 1.0.0-alpha+1 104 | 105 | - fix minor issue in toggleAttribute method 106 | - snackbar_test refactored 107 | - added flaky-on-travis tags to tests which is never fails on localhost but sometimes fails on travis 108 | - moved PromptComponent to lib folder due to Angular4 migration 109 | 110 | ## 1.0.0-alpha 111 | - Upgrade to Angular4 112 | 113 | ## 0.0.16 114 | - Added new component 115 | 116 | ## 0.0.15 117 | 118 | - Added new component `` and corresponding Directives. 119 | - Ckeditor description corrected 120 | - analysis_options became stricter 121 | - travis script now running dartfmt, if the code is not formated properly then exit 122 | 123 | ## 0.0.14 124 | - Added tests to snackbar 125 | - Changed angular2 version from ^3.1.0 to 3.1.0 126 | 127 | ## 0.0.13 128 | - Added SkawaRandomColorizePipe 129 | 130 | ## 0.0.12 131 | - Added sort logic to SkawaDataTableComponent 132 | 133 | ## 0.0.11 134 | - Modified snackbar to use material popup. 135 | - Snackbar should now be in the bottom left corner of the screen always. 136 | - Changed angular_components version from "^0.5.1" to "^0.5.3+1" 137 | 138 | ## 0.0.10 139 | 140 | - DataTable highlight event won't be triggered when selector checkbox is clicked 141 | 142 | ## 0.0.9 143 | 144 | - Changed to `DataTable` 145 | - introduction of the concept of `primaryAction`. Columns with accessors can subscript to `(trigger)` 146 | action. 147 | - rows can be `(highlight)`-ed without making a change in selection 148 | 149 | ## 0.0.8+1 150 | 151 | - Fixed a strong-mode error about missing trigger on Snackbar 152 | - Changed `DataTableAccessor` return value from `dynamic` to `String` 153 | 154 | ## 0.0.8 155 | 156 | - Add `SnackbarComponent` 157 | 158 | ## 0.0.7 159 | 160 | - Add some new functionality to ``: 161 | - `change` event is emitted when selection changes 162 | - setting `multiSelection` toggles whether only a single or multiple elements can be selected 163 | 164 | ## 0.0.6+1 165 | 166 | - fixed an issue where dart2js would warn about `??` in for loop 167 | 168 | ## 0.0.6 169 | 170 | - Added support for column renderer for `` 171 | 172 | ## 0.0.5 173 | 174 | - Added new component `` 175 | 176 | ## 0.0.4 177 | 178 | - CKEditor accepts initial value as `content` input property 179 | 180 | ## 0.0.3+1 181 | 182 | - Fixed transformer usage 183 | 184 | ## 0.0.3 185 | 186 | - Added new component `` 187 | 188 | ## 0.0.2 189 | 190 | - Added tests with travis integration 191 | - Minor changes to components and their styling -------------------------------------------------------------------------------- /skawa_components/lib/css/_skawa-material.scss: -------------------------------------------------------------------------------- 1 | //@import "skawa-palette"; 2 | @import "material-scrollbar"; 3 | @import "package:angular_components/css/material/material"; 4 | 5 | $font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !default; 6 | @import url(https://fonts.googleapis.com/css?family=Roboto); 7 | 8 | // https://material.io/guidelines/layout/structure.html#structure-app-bar 9 | $increment: 56px; 10 | $sidenav-width: $increment * 4; 11 | 12 | // https://material.io/guidelines/material-design/elevation-shadows.html#elevation-shadows-elevation-android 13 | 14 | $mat-card-subhead-font-size: $mat-font-size-button; 15 | 16 | @mixin mat-type-body() { 17 | font-size: $mat-font-size-body; 18 | font-weight: $mat-font-weight-regular; 19 | } 20 | 21 | 22 | @mixin mat-type-caption() { 23 | font-size: $mat-font-size-caption; 24 | font-weight: $mat-font-weight-regular; 25 | } 26 | 27 | 28 | @mixin mat-type-headline() { 29 | font-size: $mat-font-size-headline; 30 | font-weight: $mat-font-weight-regular; 31 | } 32 | 33 | @mixin mat-type-title() { 34 | font-size: $mat-font-size-title; 35 | font-weight: $mat-font-weight-medium; 36 | } 37 | 38 | 39 | @mixin shadow-2dp() { 40 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2), 41 | 0 3px 1px -2px rgba(0, 0, 0, 0.14), 42 | 0 1px 5px 0 rgba(0, 0, 0, 0.12); 43 | } 44 | 45 | @mixin shadow-3dp() { 46 | box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), 47 | 0 3px 3px -2px rgba(0, 0, 0, 0.14), 48 | 0 1px 8px 0 rgba(0, 0, 0, 0.12); 49 | } 50 | 51 | @mixin shadow-4dp() { 52 | box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.2), 53 | 0 1px 10px 0 rgba(0, 0, 0, 0.12), 54 | 0 2px 4px -1px rgba(0, 0, 0, 0.14); 55 | } 56 | 57 | @mixin shadow-6dp() { 58 | box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.2), 59 | 0 1px 18px 0 rgba(0, 0, 0, 0.12), 60 | 0 3px 5px -1px rgba(0, 0, 0, 0.14); 61 | } 62 | 63 | @mixin shadow-8dp() { 64 | box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.2), 65 | 0 3px 14px 2px rgba(0, 0, 0, 0.12), 66 | 0 5px 5px -3px rgba(0, 0, 0, 0.14); 67 | } 68 | 69 | @mixin shadow-16dp() { 70 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.2), 71 | 0 6px 30px 5px rgba(0, 0, 0, 0.12), 72 | 0 8px 10px -5px rgba(0, 0, 0, 0.14); 73 | } 74 | 75 | @mixin shadow-24dp() { 76 | box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.2), 77 | 0 11px 15px -7px rgba(0, 0, 0, 0.12), 78 | 0 24px 38px 3px rgba(0, 0, 0, 0.14); 79 | } 80 | 81 | /* Animations */ 82 | $animation-curve-fast-out-slow-in: cubic-bezier(0.4, 0, 0.2, 1) !default; 83 | $animation-curve-linear-out-slow-in: cubic-bezier(0, 0, 0.2, 1) !default; 84 | $animation-curve-fast-out-linear-in: cubic-bezier(0.4, 0, 1, 1) !default; 85 | 86 | @mixin material-animation-fast-out-slow-in($duration:0.2s) { 87 | transition-duration: $duration; 88 | transition-timing-function: $animation-curve-fast-out-slow-in; 89 | } 90 | 91 | @mixin material-animation-linear-out-slow-in($duration:0.2s) { 92 | transition-duration: $duration; 93 | transition-timing-function: $animation-curve-linear-out-slow-in; 94 | } 95 | 96 | @mixin material-animation-fast-out-linear-in($duration:0.2s) { 97 | transition-duration: $duration; 98 | transition-timing-function: $animation-curve-fast-out-linear-in; 99 | } 100 | 101 | @mixin material-animation-default($duration:0.2s) { 102 | transition-duration: $duration; 103 | transition-timing-function: $mat-transition-linear-slow; 104 | } 105 | 106 | /// 24px font with primary color 107 | @mixin typo-headline { 108 | @include mat-type-headline; 109 | line-height: 32px; 110 | -moz-osx-font-smoothing: grayscale; 111 | color: $mat-transparent-black; 112 | } 113 | 114 | @mixin typo-title { 115 | @include mat-type-title; 116 | font-family: $font-family; 117 | line-height: 1; 118 | letter-spacing: 0.02em; 119 | color: $mat-transparent-black; 120 | } 121 | 122 | @mixin typo-title--dense { 123 | @include typo-title; 124 | font-size: $mat-font-size-subhead; 125 | line-height: 20px; 126 | } 127 | 128 | /// 24px font with secondary color 129 | @mixin typo-headline--secondary-color { 130 | @include typo-headline; 131 | color: $mat-transparent-black; 132 | } 133 | 134 | @mixin typo-subhead { 135 | font-family: $font-family; 136 | font-size: 16px; 137 | font-weight: 400; 138 | line-height: 24px; 139 | letter-spacing: 0.04em; 140 | -moz-osx-font-smoothing: grayscale; 141 | color: $mat-transparent-black; 142 | } 143 | 144 | @mixin typo-caption { 145 | @include mat-type-caption; 146 | line-height: 1; 147 | letter-spacing: 0; 148 | color: $mat-transparent-black; 149 | } 150 | 151 | /// Decreased line height to 20px compared to default subhead 152 | @mixin typo-subhead--dense { 153 | @include typo-subhead; 154 | line-height: 20px; 155 | } 156 | 157 | @mixin typo-body { 158 | @include mat-type-body; 159 | line-height: 24px; 160 | letter-spacing: 0; 161 | color: $mat-transparent-black; 162 | } 163 | 164 | /// inline-flex screws up how line-ending whitespaces. It truncates them. 165 | /// use this where those are important 166 | @mixin ws-truncate-fix { 167 | display: inline-block; 168 | } 169 | 170 | @mixin module-main { 171 | display: block; 172 | height: 100vh; 173 | background-color: rgb(250, 250, 250); 174 | } -------------------------------------------------------------------------------- /skawa_material_components/lib/css/_skawa-material.scss: -------------------------------------------------------------------------------- 1 | //@import "skawa-palette"; 2 | @import "material-scrollbar"; 3 | @import "package:angular_components/css/material/material"; 4 | 5 | $font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif !default; 6 | @import url(https://fonts.googleapis.com/css?family=Roboto); 7 | 8 | // https://material.io/guidelines/layout/structure.html#structure-app-bar 9 | $increment: 56px; 10 | $sidenav-width: $increment * 4; 11 | 12 | // https://material.io/guidelines/material-design/elevation-shadows.html#elevation-shadows-elevation-android 13 | 14 | 15 | $mat-card-subhead-font-size: $mat-font-size-button; 16 | 17 | @mixin mat-type-body() { 18 | font-size: $mat-font-size-body; 19 | font-weight: $mat-font-weight-regular; 20 | } 21 | 22 | 23 | @mixin mat-type-caption() { 24 | font-size: $mat-font-size-caption; 25 | font-weight: $mat-font-weight-regular; 26 | } 27 | 28 | 29 | @mixin mat-type-headline() { 30 | font-size: $mat-font-size-headline; 31 | font-weight: $mat-font-weight-regular; 32 | } 33 | 34 | @mixin mat-type-title() { 35 | font-size: $mat-font-size-title; 36 | font-weight: $mat-font-weight-medium; 37 | } 38 | 39 | @mixin shadow-2dp() { 40 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2), 41 | 0 3px 1px -2px rgba(0, 0, 0, 0.14), 42 | 0 1px 5px 0 rgba(0, 0, 0, 0.12); 43 | } 44 | 45 | @mixin shadow-3dp() { 46 | box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.2), 47 | 0 3px 3px -2px rgba(0, 0, 0, 0.14), 48 | 0 1px 8px 0 rgba(0, 0, 0, 0.12); 49 | } 50 | 51 | @mixin shadow-4dp() { 52 | box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.2), 53 | 0 1px 10px 0 rgba(0, 0, 0, 0.12), 54 | 0 2px 4px -1px rgba(0, 0, 0, 0.14); 55 | } 56 | 57 | @mixin shadow-6dp() { 58 | box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.2), 59 | 0 1px 18px 0 rgba(0, 0, 0, 0.12), 60 | 0 3px 5px -1px rgba(0, 0, 0, 0.14); 61 | } 62 | 63 | @mixin shadow-8dp() { 64 | box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.2), 65 | 0 3px 14px 2px rgba(0, 0, 0, 0.12), 66 | 0 5px 5px -3px rgba(0, 0, 0, 0.14); 67 | } 68 | 69 | @mixin shadow-16dp() { 70 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.2), 71 | 0 6px 30px 5px rgba(0, 0, 0, 0.12), 72 | 0 8px 10px -5px rgba(0, 0, 0, 0.14); 73 | } 74 | 75 | @mixin shadow-24dp() { 76 | box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.2), 77 | 0 11px 15px -7px rgba(0, 0, 0, 0.12), 78 | 0 24px 38px 3px rgba(0, 0, 0, 0.14); 79 | } 80 | 81 | /* Animations */ 82 | $animation-curve-fast-out-slow-in: cubic-bezier(0.4, 0, 0.2, 1) !default; 83 | $animation-curve-linear-out-slow-in: cubic-bezier(0, 0, 0.2, 1) !default; 84 | $animation-curve-fast-out-linear-in: cubic-bezier(0.4, 0, 1, 1) !default; 85 | 86 | @mixin material-animation-fast-out-slow-in($duration:0.2s) { 87 | transition-duration: $duration; 88 | transition-timing-function: $animation-curve-fast-out-slow-in; 89 | } 90 | 91 | @mixin material-animation-linear-out-slow-in($duration:0.2s) { 92 | transition-duration: $duration; 93 | transition-timing-function: $animation-curve-linear-out-slow-in; 94 | } 95 | 96 | @mixin material-animation-fast-out-linear-in($duration:0.2s) { 97 | transition-duration: $duration; 98 | transition-timing-function: $animation-curve-fast-out-linear-in; 99 | } 100 | 101 | @mixin material-animation-default($duration:0.2s) { 102 | transition-duration: $duration; 103 | transition-timing-function: $mat-transition-linear-slow; 104 | } 105 | 106 | /// 24px font with primary color 107 | @mixin typo-headline { 108 | @include mat-type-headline; 109 | line-height: 32px; 110 | -moz-osx-font-smoothing: grayscale; 111 | color: $mat-transparent-black; 112 | } 113 | 114 | @mixin typo-title { 115 | @include mat-type-title; 116 | font-family: $font-family; 117 | line-height: 1; 118 | letter-spacing: 0.02em; 119 | color: $mat-transparent-black; 120 | } 121 | 122 | @mixin typo-title--dense { 123 | @include typo-title; 124 | font-size: $mat-font-size-subhead; 125 | line-height: 20px; 126 | } 127 | 128 | /// 24px font with secondary color 129 | @mixin typo-headline--secondary-color { 130 | @include typo-headline; 131 | color: $mat-transparent-black; 132 | } 133 | 134 | @mixin typo-subhead { 135 | font-family: $font-family; 136 | font-size: 16px; 137 | font-weight: 400; 138 | line-height: 24px; 139 | letter-spacing: 0.04em; 140 | -moz-osx-font-smoothing: grayscale; 141 | color: $mat-transparent-black; 142 | } 143 | 144 | @mixin typo-caption { 145 | @include mat-type-caption; 146 | line-height: 1; 147 | letter-spacing: 0; 148 | color: $mat-transparent-black; 149 | } 150 | 151 | /// Decreased line height to 20px compared to default subhead 152 | @mixin typo-subhead--dense { 153 | @include typo-subhead; 154 | line-height: 20px; 155 | } 156 | 157 | @mixin typo-body { 158 | @include mat-type-body; 159 | line-height: 24px; 160 | letter-spacing: 0; 161 | color: $mat-transparent-black; 162 | } 163 | 164 | /// inline-flex screws up how line-ending whitespaces. It truncates them. 165 | /// use this where those are important 166 | @mixin ws-truncate-fix { 167 | display: inline-block; 168 | } 169 | 170 | @mixin module-main { 171 | display: block; 172 | height: 100vh; 173 | background-color: rgb(250, 250, 250); 174 | } -------------------------------------------------------------------------------- /skawa_components/test/language_direction_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:pageloader/html.dart'; 3 | import 'package:test/test.dart'; 4 | import 'package:angular/core.dart'; 5 | import 'package:skawa_components/directives/language_direction_directive.dart'; 6 | import 'package:angular_test/angular_test.dart'; 7 | import 'language_direction_test.template.dart' as ng; 8 | 9 | part 'language_direction_test.g.dart'; 10 | 11 | void main() { 12 | ng.initReflector(); 13 | tearDown(disposeAnyRunningTest); 14 | group('LanguageDirection | ', () { 15 | final String textAlign = 'text-align'; 16 | final String direction = 'direction'; 17 | final String start = 'start'; 18 | final String ltr = 'ltr'; 19 | test('initialization on div element', () async { 20 | final fixture = await NgTestBed().create(); 21 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 22 | final pageObject = DivTestPO.create(context); 23 | expect(pageObject.languageDirection.computedStyle[textAlign], start); 24 | expect(pageObject.languageDirection.computedStyle[direction], ltr); 25 | }); 26 | test('setLanguageDirection on div element by latin text', () async { 27 | final fixture = await NgTestBed() 28 | .create(beforeChangeDetection: (testElement) => testElement.content = 'cat'); 29 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 30 | final pageObject = DivTestPO.create(context); 31 | await pageObject.languageDirection.click(); 32 | expect(pageObject.languageDirection.computedStyle[textAlign], start); 33 | expect(pageObject.languageDirection.computedStyle[direction], ltr); 34 | }); 35 | test('setLanguageDirection on div element by arabic text', () async { 36 | final fixture = await NgTestBed() 37 | .create(beforeChangeDetection: (testElement) => testElement.content = 'عن فكانت اسبوعين'); 38 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 39 | final pageObject = DivTestPO.create(context); 40 | await pageObject.languageDirection.click(); 41 | expect(pageObject.languageDirection.computedStyle[textAlign], 'right'); 42 | expect(pageObject.languageDirection.computedStyle[direction], 'rtl'); 43 | }); 44 | test('initialization on div element', () async { 45 | final fixture = await NgTestBed().create(); 46 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 47 | final pageObject = InputTestPO.create(context); 48 | expect(pageObject.languageDirection.computedStyle[textAlign], start); 49 | expect(pageObject.languageDirection.computedStyle[direction], ltr); 50 | }); 51 | test('setLanguageDirection on input element by latin text', () async { 52 | final fixture = await NgTestBed() 53 | .create(beforeChangeDetection: (testElement) => testElement.content = 'cat'); 54 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 55 | final pageObject = InputTestPO.create(context); 56 | await pageObject.languageDirection.click(); 57 | expect(pageObject.languageDirection.computedStyle[textAlign], start); 58 | expect(pageObject.languageDirection.computedStyle[direction], ltr); 59 | }); 60 | test('setLanguageDirection on input element by arabic text', () async { 61 | final fixture = await NgTestBed() 62 | .create(beforeChangeDetection: (testElement) => testElement.content = 'عن فكانت اسبوعين'); 63 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 64 | final pageObject = InputTestPO.create(context); 65 | await pageObject.languageDirection.click(); 66 | expect(pageObject.languageDirection.computedStyle[textAlign], start); 67 | expect(pageObject.languageDirection.computedStyle[direction], 'rtl'); 68 | }); 69 | }); 70 | } 71 | 72 | @Component( 73 | selector: 'test', 74 | template: 75 | '''
''', 76 | directives: [LanguageDirectionDirective]) 77 | class DivTemplateComponent { 78 | String content; 79 | } 80 | 81 | @PageObject() 82 | @CheckTag('test') 83 | abstract class DivTestPO { 84 | DivTestPO(); 85 | 86 | factory DivTestPO.create(PageLoaderElement context) = $DivTestPO.create; 87 | 88 | @ByClass('dir') 89 | PageLoaderElement get languageDirection; 90 | } 91 | 92 | @Component( 93 | selector: 'test', 94 | template: 95 | '''''', 96 | directives: [LanguageDirectionDirective]) 97 | class InputTemplateComponent { 98 | String content; 99 | } 100 | 101 | @PageObject() 102 | @CheckTag('test') 103 | abstract class InputTestPO { 104 | InputTestPO(); 105 | 106 | factory InputTestPO.create(PageLoaderElement context) = $InputTestPO.create; 107 | 108 | @ByTagName('input') 109 | PageLoaderElement get languageDirection; 110 | } 111 | -------------------------------------------------------------------------------- /skawa_components/test/list_wrapper_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | import 'package:pageloader/html.dart'; 3 | import 'package:test/test.dart'; 4 | import 'package:angular_test/angular_test.dart'; 5 | import 'package:angular/core.dart'; 6 | import 'package:skawa_components/list_wrapper/list_wrapper.dart'; 7 | import 'package:skawa_components/list_wrapper/skawa_for_directive.dart'; 8 | import 'list_wrapper_test.template.dart' as ng; 9 | 10 | part 'list_wrapper_test.g.dart'; 11 | 12 | void main() { 13 | ng.initReflector(); 14 | tearDown(disposeAnyRunningTest); 15 | 16 | setUpAll(() {}); 17 | group('SkawaListWrapperComponent', () { 18 | final testBed = NgTestBed(); 19 | NgTestFixture fixture; 20 | TestPO pageObject; 21 | setUp(() async { 22 | fixture = await testBed.create(); 23 | final context = HtmlPageLoaderElement.createFromElement(fixture.rootElement); 24 | pageObject = TestPO.create(context); 25 | }); 26 | test('initialization', () { 27 | expect(pageObject.listWrapper.spanList.length, 8); 28 | expect(pageObject.listWrapper.spanList.first.innerText, sampleList[0]); 29 | }); 30 | test('scroll down 4 item', () async { 31 | await fixture.update((c) => c.scroll(350)); 32 | expect(pageObject.listWrapper.spanList.first.innerText, sampleList[2]); 33 | }); 34 | test('reverse the source', () async { 35 | await fixture.update((c) => c.reverse()); 36 | expect(pageObject.listWrapper.spanList.length, 8); 37 | expect(pageObject.listWrapper.spanList.first.innerText, sampleList.last); 38 | }); 39 | test('reverse the source, then scroll down', () async { 40 | await fixture.update((c) => c.reverse()); 41 | await fixture.update((c) => c.scroll(700)); 42 | expect(pageObject.listWrapper.spanList.length, 8); 43 | expect(pageObject.listWrapper.spanList.first.innerText, sampleList[sampleList.length - 9]); 44 | }); 45 | }, skip: 'Scrolling is not consistent on ci'); 46 | } 47 | 48 | @Component( 49 | selector: 'test', 50 | template: ''' 51 | 52 | {{item}} 53 | 54 | 55 | 56 | ''', 57 | styles: [':host list-wrapper{height: 300px;}', 'host: {max-height:300px;}'], 58 | directives: [SkawaForDirective, SkawaListWrapperComponent]) 59 | class ListWrapperTestComponent { 60 | SkawaForSource list = SkawaForSource(sampleList, true, 75); 61 | 62 | @ViewChild(SkawaForDirective) 63 | SkawaForDirective skawaForDirective; 64 | 65 | @ViewChild(SkawaListWrapperComponent) 66 | SkawaListWrapperComponent listWrapper; 67 | 68 | void scroll(int scrollTop) => listWrapper.htmlElement.scrollTop = scrollTop; 69 | 70 | void reverse() => list = SkawaForSource(list.source.reversed.toList(), true, 75); 71 | 72 | void double() { 73 | List listToAdd = list.source; 74 | list = SkawaForSource(List.from(listToAdd)..addAll(listToAdd), true, 75); 75 | } 76 | } 77 | 78 | List sampleList = 79 | '''Lorem ipsum dolor sit amet, quo at esse clita, dolorum accusata mei id. Dicam quidam petentium mea et, sit cu numquam tractatos, nec et etiam viderer legimus. Nam eu malis graece dissentiunt. Nibh mutat discere ius id. In everti menandri vix, duo tale altera molestiae ei, vim veritus molestie no. Ex usu eius deseruisse moderatius, nec te aeque detracto mentitum. 80 | Te semper docendi invenire mei, laudem primis graeco eos te. Epicurei eloquentiam ius an, vim ex sumo wisi eloquentiam. Putant audiam expetendis an has, habeo abhorreant qui eu. Per altera propriae ne, nec at magna aliquam urbanitas. Dolorum propriae deterruisset eu vis, tale congue decore ei usu. 81 | Eu qui illum novum elitr, prodesset persequeris eu mea. Ad eum postulant mediocrem hendrerit, vim omnes facete dissentiunt no. Te erant cotidieque vis. Mel ex atqui sanctus, ut has nusquam luptatum. 82 | Ad per ferri sonet copiosae, veri indoctum ut mea. Melius senserit recteque eam et, homero vivendo ea eos, facilis signiferumque per eu. Id eros aliquip hendrerit ius. Diam oportere intellegat cum te. His ad delenit lobortis interesset. 83 | Sit id case scaevola. Ad velit appareat vel, cu sea natum facilisi. Mel semper persius ocurreret at. Est diam stet fabellas ex, sea ullum iriure comprehensam et, ea atomorum sensibus mel. Pri ea meliore fastidii. Ex percipit assentior his, ut vix vidisse offendit. ''' 84 | .split(' '); 85 | 86 | @PageObject() 87 | @CheckTag('test') 88 | abstract class TestPO { 89 | @ByTagName('list-wrapper') 90 | ListWrapperPO get listWrapper; 91 | 92 | @ByTagName('div') 93 | List get divList; 94 | 95 | TestPO(); 96 | 97 | factory TestPO.create(PageLoaderElement context) = $TestPO.create; 98 | } 99 | 100 | @PageObject() 101 | abstract class ListWrapperPO { 102 | ListWrapperPO(); 103 | 104 | factory ListWrapperPO.create(PageLoaderElement context) = $ListWrapperPO.create; 105 | 106 | @ByTagName('span') 107 | List get spanList; 108 | 109 | @root 110 | PageLoaderElement get rootElement; 111 | } 112 | -------------------------------------------------------------------------------- /skawa_components/lib/code_mirror/base_loader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:html'; 3 | 4 | /// this class 5 | 6 | class BaseLoader { 7 | Future load(LoadedElement linkToLoad, 8 | {void loadCallback(), void errorCallback(), bool force = false, bool skipCallBack = false}) async { 9 | if (force) { 10 | _cleanDom(linkToLoad); 11 | _removeCachedLoadResult(linkToLoad); 12 | } 13 | if (_stillLoading(linkToLoad.linkToLoad)) { 14 | await _waitForLoad(linkToLoad.linkToLoad); 15 | await load(linkToLoad, 16 | loadCallback: loadCallback, errorCallback: errorCallback, force: false, skipCallBack: skipCallBack); 17 | return false; 18 | } 19 | if (_previouslyLoaded(linkToLoad.linkToLoad)) { 20 | // will still trigger callbacks based on the previous state of script 21 | if (!skipCallBack) _triggerCallbacks(linkToLoad.linkToLoad, loadCallback, errorCallback); 22 | return false; 23 | } 24 | await _domLoadScript(linkToLoad, loadCallback, errorCallback, skipCallBack); 25 | return true; 26 | } 27 | 28 | Future loadList(List linkToLoadList, 29 | {void loadCallback(), void errorCallback(), bool force = false}) async { 30 | // print('linkToLoadList: ${linkToLoadList.map((LoadedElement elem) => elem.linkToLoad).toList()}'); 31 | await Future.wait(linkToLoadList.map((LoadedElement linkToLoad) => load(linkToLoad, 32 | loadCallback: loadCallback, errorCallback: errorCallback, force: force, skipCallBack: true))).catchError((_) { 33 | (errorCallback ?? _noop)(); 34 | linkToLoadList.forEach(_cleanDom); 35 | }); 36 | loadCallback != null ? loadCallback() : _noop(); 37 | } 38 | 39 | void _triggerCallbacks(String linkToLoad, void loadCallback(), void errorCallback()) { 40 | // will still trigger callbacks based on the previous state of script 41 | if (loadCallback != null && _loadedResult[linkToLoad]) loadCallback(); 42 | if (errorCallback != null && !_loadedResult[linkToLoad]) errorCallback(); 43 | } 44 | 45 | Future _waitForLoad(String linkToLoad) => _loadingResult[linkToLoad].first; 46 | 47 | bool _previouslyLoaded(String linkToLoad) => _loadedResult.containsKey(linkToLoad); 48 | 49 | bool _stillLoading(String linkToLoad) => _loadingResult.containsKey(linkToLoad); 50 | 51 | void _cleanDom(LoadedElement linkToLoad) => 52 | document.querySelectorAll(linkToLoad.type).where((Element el) => linkToLoad.isSameElement(el)).forEach(_remove); 53 | 54 | void _removeCachedLoadResult(LoadedElement linkToLoad) { 55 | _loadedResult.remove(linkToLoad.linkToLoad); 56 | _loadingResult.remove(linkToLoad.linkToLoad); 57 | } 58 | 59 | Future _domLoadScript(LoadedElement linkToLoad, void loadCallback(), void errorCallback(), bool skipCallBack) async { 60 | linkToLoad.createNewElement(); 61 | _loadingResult[linkToLoad.linkToLoad] = linkToLoad._element.onLoad; 62 | Completer loadCompleter = Completer(); 63 | // set listener 64 | linkToLoad._element.onLoad.listen((_) { 65 | _loadedResult[linkToLoad.linkToLoad] = true; 66 | _loadingResult.remove(linkToLoad.linkToLoad); 67 | // print('loaded: ${linkToLoad.linkToLoad}'); 68 | if (!loadCompleter.isCompleted) loadCompleter.complete(); 69 | if (!skipCallBack) loadCallback != null ? loadCallback() : _noop(); 70 | }).onError((_) { 71 | // print('error: ${linkToLoad.linkToLoad} || $_'); 72 | errorCallback != null ? errorCallback() : _noop(); 73 | }); 74 | // some trickery - need to append first 75 | document.head.append(linkToLoad._element); 76 | linkToLoad.updateElement(); 77 | await loadCompleter.future; 78 | } 79 | 80 | static void _remove(Element element) => element.remove(); 81 | 82 | static void _noop([Event ev]) {} 83 | 84 | /// scripts that are loaded and loadCallbacks are triggered 85 | static final Map _loadedResult = Map(); 86 | 87 | /// scripts are being loaded but loadCallback has not yet triggered 88 | static final Map _loadingResult = Map(); 89 | } 90 | 91 | abstract class LoadedElement { 92 | Element _element; 93 | 94 | Element get element => _element; 95 | 96 | String get type; 97 | 98 | String get linkToLoad; 99 | 100 | void createNewElement(); 101 | 102 | bool isSameElement(Element el); 103 | 104 | void updateElement(); 105 | } 106 | 107 | class LinkLoader extends LoadedElement { 108 | @override 109 | final String type = 'link'; 110 | 111 | @override 112 | final String linkToLoad; 113 | 114 | LinkLoader(this.linkToLoad); 115 | 116 | @override 117 | void createNewElement() => _element = LinkElement()..rel = 'stylesheet'; 118 | 119 | @override 120 | bool isSameElement(Element el) => el is LinkElement && el.href == linkToLoad; 121 | 122 | @override 123 | void updateElement() => (element as LinkElement).href = linkToLoad; 124 | } 125 | 126 | class ScriptLoader extends LoadedElement { 127 | @override 128 | final String type = 'script'; 129 | 130 | @override 131 | final String linkToLoad; 132 | 133 | ScriptLoader(this.linkToLoad); 134 | 135 | @override 136 | void createNewElement() => _element = ScriptElement() 137 | ..defer = true 138 | ..type = 'application/javascript'; 139 | 140 | @override 141 | bool isSameElement(Element el) => el is ScriptElement && el.src == linkToLoad; 142 | 143 | @override 144 | void updateElement() => (element as ScriptElement).src = linkToLoad; 145 | } 146 | --------------------------------------------------------------------------------