uki 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,11 +36,13 @@
36
36
  _scrollableV: true,
37
37
  _scrollableH: false,
38
38
  _scrollV: false,
39
- _scrollH: false
39
+ _scrollH: false,
40
+ _sbV: false,
41
+ _sbH: false
40
42
  });
41
43
  };
42
44
 
43
- uki.addProps(this, ['scrollableV', 'scrollableH']);
45
+ uki.addProps(this, ['scrollableV', 'scrollableH', 'scrollH', 'scrollV']);
44
46
 
45
47
  this.rectForChild = function() { return this._rectForChild; };
46
48
  this.clientRect = function() { return this._clientRect; };
@@ -56,8 +58,14 @@
56
58
  };
57
59
  }, this);
58
60
 
59
- uki.delegateProp(this, 'scrollTop', '_dom');
60
- uki.delegateProp(this, 'scrollLeft', '_dom');
61
+ uki.each(['scrollTop', 'scrollLeft'], function(i, name) {
62
+ this[name] = function(v) {
63
+ if (v == undefined) return this._dom[name];
64
+ this._dom[name] = v;
65
+ this.trigger('scroll', { source: this });
66
+ return this;
67
+ };
68
+ }, this);
61
69
 
62
70
  this.visibleRect = function() {
63
71
  var tmpRect = this._clientRect.clone();
@@ -87,8 +95,8 @@
87
95
  sh = this._scrollableH ? cw > this._rect.width : false,
88
96
  sv = this._scrollableV ? ch > this._rect.height : false;
89
97
 
90
- this._scrollH = sh;
91
- this._scrollV = sv;
98
+ this._sbH = sh || this._scrollH;
99
+ this._sbV = sv || this._scrollV;
92
100
  this._clientRect = new Rect( this._rect.width + (sv ? -1 : 0) * scrollWidth,
93
101
  this._rect.height + (sh ? -1 : 0) * scrollWidth );
94
102
  this._rectForChild = new Rect( this._rect.width + (sv && !widthIncludesScrollBar ? -1 : 0) * scrollWidth,
@@ -121,14 +129,14 @@
121
129
  this._layoutDom = function(rect) {
122
130
  this._updateClientRects();
123
131
 
124
- if (this._layoutScrollH !== this._scrollH) {
125
- this._dom.style.overflowX = this._scrollH ? 'scroll' : 'hidden';
126
- this._layoutScrollH = this._scrollH;
132
+ if (this._layoutScrollH !== this._sbH) {
133
+ this._dom.style.overflowX = this._sbH ? 'scroll' : 'hidden';
134
+ this._layoutScrollH = this._sbH;
127
135
  }
128
136
 
129
- if (this._layoutScrollV !== this._scrollV) {
130
- this._dom.style.overflowY = this._scrollV ? 'scroll' : 'hidden';
131
- this._layoutScrollV = this._scrollV;
137
+ if (this._layoutScrollV !== this._sbV) {
138
+ this._dom.style.overflowY = this._sbV ? 'scroll' : 'hidden';
139
+ this._layoutScrollV = this._sbV;
132
140
  }
133
141
 
134
142
  Base._layoutDom.call(this, rect);
@@ -1,96 +1,118 @@
1
1
  uki.view.table.Column = uki.newClass(uki.view.Observable, new function() {
2
- var proto = this;
3
-
4
- proto._width = 100;
5
- proto._offset = 0;
6
- proto._position = 0;
7
- proto._minWidth = 40;
8
- proto._css = 'overflow:hidden;float:left;font-size:11px;line-height:11px;white-space:nowrap;text-overflow:ellipsis;';
9
- proto._inset = new Inset(3, 5);
2
+ this._width = 100;
3
+ this._offset = 0;
4
+ this._position = 0;
5
+ this._minWidth = 0;
6
+ this._maxWidth = 0;
7
+ this._css = 'float:left;white-space:nowrap;text-overflow:ellipsis;';
8
+ this._inset = new Inset(3, 5);
10
9
 
11
- proto.init = function() {};
10
+ this.init = function() {};
11
+
12
+ uki.addProps(this, ['position', 'css', 'formatter', 'label', 'resizable', 'maxWidth', 'minWidth', 'maxWidth']);
12
13
 
13
- uki.addProps(proto, ['position', 'css', 'formatter', 'label', 'resizable', 'maxWidth', 'minWidth']);
14
+ this.template = function(v) {
15
+ if (v === undefined) return this._template = this._template || uki.theme.template('table-cell');
16
+ this._template = v;
17
+ return this;
18
+ };
19
+
20
+ this.headerTemplate = function(v) {
21
+ if (v === undefined) return this._headerTemplate = this._headerTemplate || uki.theme.template('table-header-cell');
22
+ this._headerTemplate = v;
23
+ return this;
24
+ };
14
25
 
15
26
  /**
16
27
  * @fires event:beforeResize
17
28
  * @fires event:resize
18
29
  */
19
- proto.width = uki.newProp('_width', function(w) {
30
+ this.width = uki.newProp('_width', function(w) {
31
+ var e = {
32
+ oldWidth: this._width,
33
+ source: this
34
+ };
20
35
  this._width = this._normailizeWidth(w);
21
- this.trigger('beforeResize', {source: this});
22
- if (this._stylesheet) {
36
+ e.newWidth = this._width;
37
+ this.trigger('beforeResize', e);
38
+ if (this._stylesheet && e.newWidth != e.oldWidth) {
23
39
  var rules = this._stylesheet.styleSheet ? this._stylesheet.styleSheet.rules : this._stylesheet.sheet.cssRules;
24
40
  rules[0].style.width = this._clientWidth() + PX;
25
41
  }
26
- this.trigger('resize', {source: this});
42
+ this.trigger('resize', e);
27
43
  });
28
44
 
29
- proto._bindToDom = uki.F;
45
+ this._bindToDom = uki.F;
30
46
 
31
- proto._normailizeWidth = function(w) {
47
+ this._normailizeWidth = function(w) {
32
48
  if (this._maxWidth) w = MIN(this._maxWidth, w);
33
49
  if (this._minWidth) w = MAX(this._minWidth, w);
34
50
  return w;
35
51
  };
36
52
 
37
- proto.inset = uki.newProp('_inset', function(i) {
53
+ this.inset = uki.newProp('_inset', function(i) {
38
54
  this._inset = Inset.create(i);
39
55
  });
40
56
 
41
- proto.render = function(row, rect, i) {
42
- if (!this._template) this._template = this._buildTemplate(rect);
43
- this._template[1] = this._formatter ? this._formatter(row[this._position], row) : row[this._position];
44
- return this._template.join('')
57
+ this.render = function(row, rect, i) {
58
+ this._prerenderedTemplate || this._prerenderTemplate(rect);
59
+ this._prerenderedTemplate[1] = this._formatter ? this._formatter(row[this._position], row) : row[this._position];
60
+ return this._prerenderedTemplate.join('');
61
+ };
62
+
63
+ this.appendResizer = function(dom, height) {
64
+ var resizer = uki.theme.dom('resizer', height);
65
+ dom.appendChild(resizer);
66
+ return resizer;
67
+ };
68
+
69
+ this.renderHeader = function(height) {
70
+ this._className || this._initStylesheet();
71
+ var x = this.headerTemplate().render({
72
+ data: '<div style="overflow:hidden;text-overflow:ellipsis;">' + this.label() + '</div>',
73
+ style: this._cellStyle(uki.dom.offset.boxModel ? height - 1 : height),
74
+ className: this._className
75
+ });
76
+ return x;
45
77
  };
46
78
 
47
- proto.renderHeader = function(height) {
48
- if (!this._headerTemplate) this._headerTemplate = this._buildHeaderTemplate(height);
49
- var template = this._headerTemplate;
50
- template[1] = this.label();
51
- return template.join('');
79
+ this._prerenderTemplate = function(rect) {
80
+ this._className || this._initStylesheet();
81
+ this._prerenderedTemplate = this.template().render({
82
+ data: '\u0001\u0001',
83
+ style: 'overflow:hidden;' + this._cellStyle(rect.height),
84
+ className: this._className
85
+ }).split('\u0001');
52
86
  };
53
87
 
54
- proto._clientWidth = function() {
88
+ this._cellPadding = function() {
89
+ var inset = this._inset;
90
+ return ['padding:', inset.top, 'px ', inset.right, 'px ', inset.bottom, 'px ', inset.left, 'px;'].join('');
91
+ };
92
+
93
+ this._cellStyle = function(height) {
94
+ var h = 'height:' + (height - (uki.dom.offset.boxModel ? this._inset.height() : 0)) + 'px;';
95
+ return this._css + this._cellPadding() + ';' + h;
96
+ };
97
+
98
+ this._clientWidth = function() {
55
99
  return this._width - (uki.dom.offset.boxModel ? this._inset.width() + 1 : 0);
56
100
  };
57
101
 
58
- proto._initStylesheet = function() {
59
- uki.dom.offset.initializeBoxModel();
102
+ this._initStylesheet = function() {
60
103
  if (!this._className) {
104
+ uki.dom.offset.initializeBoxModel();
61
105
  this._className = 'uki-table-column-' + (++uki.dom.guid);
62
106
  var css = '.' + this._className + ' {width:' + this._clientWidth() + 'px;}';
63
107
  this._stylesheet = uki.dom.createStylesheet(css);
64
108
  }
65
109
  };
66
-
67
- proto._buildHeaderTemplate = function(headerHeight) {
68
- this._initStylesheet();
69
- var border = 'border:1px solid #CCC;border-top: none;border-left:none;',
70
- inset = this._inset,
71
- padding = ['padding:', inset.top, 'px ', inset.right, 'px ', inset.bottom, 'px ', inset.left, 'px;'].join(''),
72
- height = 'height:' + (headerHeight - (uki.dom.offset.boxModel ? inset.height() + 1 : 0)) + 'px;',
73
- tagOpening = ['<div style="position:relative;', border, padding, height, this._css, '" class="',this._className,'">'].join('');
74
-
75
- return [tagOpening, '', '</div>'];
76
- };
77
-
78
- proto._buildTemplate = function(rect) {
79
- this._initStylesheet();
80
- var inset = this._inset,
81
- border = 'border-right:1px solid #CCC;',
82
- padding = ['padding:', inset.top, 'px ', inset.right, 'px ', inset.bottom, 'px ', inset.left, 'px;'].join(''),
83
- height = 'height:' + (rect.height - (uki.dom.offset.boxModel ? inset.height() : 0)) + 'px;',
84
- tagOpening = ['<div style="', border, padding, height, this._css, '" class="',this._className,'">'].join('');
85
- return [tagOpening, '', '</div>'];
86
- };
87
110
  });
88
111
 
89
112
  uki.view.table.NumberColumn = uki.newClass(uki.view.table.Column, new function() {
90
- var Base = uki.view.table.Column.prototype,
91
- proto = this;
113
+ var Base = uki.view.table.Column.prototype;
92
114
 
93
- proto._css = Base._css + 'text-align:right;';
115
+ this._css = Base._css + 'text-align:right;';
94
116
  });
95
117
 
96
118
  uki.view.table.CustomColumn = uki.view.table.Column;
@@ -29,10 +29,7 @@ uki.view.table.Header = uki.newClass(uki.view.Label, new function() {
29
29
  for (var i=0, column, resizer, offset = 0; i < this._columns.length; i++) {
30
30
  column = this._columns[i];
31
31
  if (column.resizable()) {
32
- resizer = uki.theme.dom('resizer', {height: this.rect().height});
33
- resizer.style.right = -2 + PX;
34
- // resizer.style.background = 'red';
35
- this._label.childNodes[i].appendChild(resizer);
32
+ var resizer = column.appendResizer(this._label.childNodes[i], this.rect().height);
36
33
  this._bindResizerDrag(resizer, i);
37
34
  }
38
35
  };
@@ -1,25 +1,15 @@
1
1
  include('../list/render.js');
2
2
 
3
3
  uki.view.table.Render = uki.newClass(uki.view.list.Render, new function() {
4
-
5
- var proto = this;
6
-
7
- proto.init = function(table) {
4
+ this.init = function(table) {
8
5
  this._table = table;
9
6
  };
10
7
 
11
- proto.render = function(row, rect, i) {
12
- if (!this._template) this._template = this._buildTemplate(rect);
8
+ this.render = function(row, rect, i) {
13
9
  var table = this._table,
14
10
  columns = table.columns();
15
- this._template[1] = uki.map(columns, function(val, j) {
11
+ return uki.map(columns, function(val, j) {
16
12
  return columns[j].render(row, rect, i);
17
13
  }).join('');
18
- return this._template.join('');
19
- };
20
-
21
- proto._buildTemplate = function(rect) {
22
- var tagOpening = ['<div style="position:relwidth:100%;height:', rect.height, 'px">'].join(',');
23
- return ['', '', '']
24
14
  };
25
15
  });
@@ -12,10 +12,23 @@ uki.view.declare('uki.view.Table', uki.view.Container, function(Base) {
12
12
 
13
13
  uki.each(propertiesToDelegate, function(i, name) { uki.delegateProp(this, name, '_list'); }, this);
14
14
 
15
+ this._setup = function() {
16
+ this._columns = [];
17
+ Base._setup.call(this);
18
+ };
19
+
20
+ this._style = function(name, value) {
21
+ this._header.style(name, value);
22
+ return Base._style.call(this, name, value);
23
+ };
24
+
15
25
  this.columns = uki.newProp('_columns', function(c) {
26
+ for (var i = 0; i < this._columns.length; i++) {
27
+ this._columns[i].unbind();
28
+ }
16
29
  this._columns = uki.build(c);
17
30
  this._totalWidth = 0;
18
- for (var i = 0; i < this._columns.length; i++) {
31
+ for (i = 0; i < this._columns.length; i++) {
19
32
  this._columns[i].position(i);
20
33
  this._columns[i].bind('beforeResize', uki.proxy(function() {
21
34
  this._updateTotalWidth();
@@ -32,8 +45,8 @@ uki.view.declare('uki.view.Table', uki.view.Container, function(Base) {
32
45
  this._columns[i].position(i);
33
46
  this._totalWidth += this._columns[i].width();
34
47
  };
35
- this._list.minSize(new Size(this._totalWidth, 0));
36
- this._list.rect(new Rect(this._totalWidth, this._list.height()));
48
+ this._list.minSize(new Size(this._totalWidth, this._list.minSize().height));
49
+ // this._list.rect(new Rect(this._totalWidth, this._list.height()));
37
50
  this._header.minSize(new Size(this._totalWidth, 0));
38
51
  };
39
52
 
@@ -36,7 +36,7 @@ uki.view.declare('uki.view.Toolbar', uki.view.Container, function(Base) {
36
36
  flowML = { view: 'HFlow', rect: flowRect, anchors: 'left top right', className: 'toolbar-flow', hidePartlyVisible: true },
37
37
  moreML = { view: 'Button', rect: moreRect, anchors: 'right top', className: 'toolbar-button', visible: false, backgroundPrefix: 'toolbar-more-', text: '>>', focusable: false },
38
38
  popupML = { view: 'Popup', rect: '0 0', anchors: 'right top', className: 'toolbar-popup', background: 'theme(toolbar-popup)',
39
- childViews: { view: 'VFlow', rect: '0 0', anchors: 'right top left bottom' }
39
+ childViews: { view: 'VFlow', rect: '0 5 0 0', anchors: 'right top left bottom' }
40
40
  };
41
41
 
42
42
  this._flow = uki.build(flowML)[0];
@@ -58,14 +58,14 @@ uki.view.declare('uki.view.Toolbar', uki.view.Container, function(Base) {
58
58
  if (currentWidth > maxWith) missing.push(i);
59
59
  };
60
60
  var newButtons = uki.map(missing, function(i) {
61
- var descr = { html: childViews[i].html(), backgroundPrefix: 'toolbar-popup-' };
61
+ var descr = { html: childViews[i].html(), backgroundPrefix: 'toolbar-popup-button-' };
62
62
  uki.each(['fontSize', 'fontWeight', 'color', 'textAlign', 'inset'], function(j, name) {
63
63
  descr[name] = uki.attr(childViews[i], name);
64
64
  });
65
65
  return this._createButton(descr);
66
66
  }, this);
67
67
  uki('VFlow', this._popup).childViews(newButtons).resizeToContents('width height');
68
- this._popup.resizeToContents('width height').toggle();
68
+ this._popup.resizeToContents('width height').height(this._popup.height() + 5).toggle();
69
69
  };
70
70
 
71
71
  this._updateMoreVisible = function() {
@@ -85,8 +85,10 @@ uki.view.declare('uki.view.Toolbar', uki.view.Container, function(Base) {
85
85
  };
86
86
 
87
87
  this._createButton = function(descr) {
88
+ var rect = this.rect().clone().normalize();
89
+ rect.width = 100;
88
90
  return uki.extend({
89
- view: 'Button', rect: new Rect(100, this.rect().height), focusable: false, align: 'left',
91
+ view: 'Button', rect: rect, focusable: false, align: 'left',
90
92
  anchors: 'left top', backgroundPrefix: 'toolbar-button-', autosizeToContents: 'width', focusable: false
91
93
  }, descr);
92
94
  };
@@ -2,9 +2,9 @@
2
2
 
3
3
  uki.view.declare('<%= "#{package_name}." if package_name %><%= class_name %>', uki.view.Base, function(Base) {
4
4
 
5
- this._createDom = function() {
6
- Base._createDom.call(this);
7
- // add your custom code here
8
- };
5
+ this._createDom = function() {
6
+ Base._createDom.call(this);
7
+ // add your custom code here
8
+ };
9
9
 
10
10
  });
data/uki.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{uki}
8
- s.version = "1.0.0"
8
+ s.version = "1.0.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Vladimir Kolesnikov"]
12
- s.date = %q{2010-03-22}
12
+ s.date = %q{2010-03-29}
13
13
  s.default_executable = %q{uki}
14
14
  s.description = %q{Project creation, dev server, testing, building for uki apps}
15
15
  s.email = %q{voloko@gmail.com}
@@ -101,6 +101,7 @@ Gem::Specification.new do |s|
101
101
  "frameworks/uki/src/uki-core/view/styleable.js",
102
102
  "frameworks/uki/src/uki-core/view/utils.js",
103
103
  "frameworks/uki/src/uki-data.js",
104
+ "frameworks/uki/src/uki-data/ajax.js",
104
105
  "frameworks/uki/src/uki-data/data.js",
105
106
  "frameworks/uki/src/uki-data/model.js",
106
107
  "frameworks/uki/src/uki-data/observable.js",
@@ -109,9 +110,9 @@ Gem::Specification.new do |s|
109
110
  "frameworks/uki/src/uki-more/more/utils.js",
110
111
  "frameworks/uki/src/uki-more/more/view.js",
111
112
  "frameworks/uki/src/uki-more/more/view/listContainer.js",
112
- "frameworks/uki/src/uki-more/more/view/multiselectList.js",
113
113
  "frameworks/uki/src/uki-more/more/view/radioButton.js",
114
114
  "frameworks/uki/src/uki-more/more/view/splitTable.js",
115
+ "frameworks/uki/src/uki-more/more/view/splitTable/render.js",
115
116
  "frameworks/uki/src/uki-more/more/view/toggleButton.js",
116
117
  "frameworks/uki/src/uki-more/more/view/treeList.js",
117
118
  "frameworks/uki/src/uki-more/more/view/treeList/render.js",
@@ -256,7 +257,7 @@ Gem::Specification.new do |s|
256
257
  s.homepage = %q{http://github.com/voloko/uki}
257
258
  s.rdoc_options = ["--charset=UTF-8"]
258
259
  s.require_paths = ["lib"]
259
- s.rubygems_version = %q{1.3.5}
260
+ s.rubygems_version = %q{1.3.6}
260
261
  s.summary = %q{uki development tools}
261
262
 
262
263
  if s.respond_to? :specification_version then
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uki
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 1
9
+ version: 1.0.1
5
10
  platform: ruby
6
11
  authors:
7
12
  - Vladimir Kolesnikov
@@ -9,39 +14,49 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-03-22 00:00:00 +03:00
17
+ date: 2010-03-29 00:00:00 +04:00
13
18
  default_executable: uki
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: sinatra
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
23
29
  version: "0"
24
- version:
30
+ type: :runtime
31
+ version_requirements: *id001
25
32
  - !ruby/object:Gem::Dependency
26
33
  name: commander
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - ">="
32
38
  - !ruby/object:Gem::Version
39
+ segments:
40
+ - 4
41
+ - 0
42
+ - 1
33
43
  version: 4.0.1
34
- version:
44
+ type: :runtime
45
+ version_requirements: *id002
35
46
  - !ruby/object:Gem::Dependency
36
47
  name: jspec
37
- type: :runtime
38
- version_requirement:
39
- version_requirements: !ruby/object:Gem::Requirement
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
40
50
  requirements:
41
51
  - - ">="
42
52
  - !ruby/object:Gem::Version
53
+ segments:
54
+ - 3
55
+ - 3
56
+ - 2
43
57
  version: 3.3.2
44
- version:
58
+ type: :runtime
59
+ version_requirements: *id003
45
60
  description: Project creation, dev server, testing, building for uki apps
46
61
  email: voloko@gmail.com
47
62
  executables:
@@ -134,6 +149,7 @@ files:
134
149
  - frameworks/uki/src/uki-core/view/styleable.js
135
150
  - frameworks/uki/src/uki-core/view/utils.js
136
151
  - frameworks/uki/src/uki-data.js
152
+ - frameworks/uki/src/uki-data/ajax.js
137
153
  - frameworks/uki/src/uki-data/data.js
138
154
  - frameworks/uki/src/uki-data/model.js
139
155
  - frameworks/uki/src/uki-data/observable.js
@@ -142,9 +158,9 @@ files:
142
158
  - frameworks/uki/src/uki-more/more/utils.js
143
159
  - frameworks/uki/src/uki-more/more/view.js
144
160
  - frameworks/uki/src/uki-more/more/view/listContainer.js
145
- - frameworks/uki/src/uki-more/more/view/multiselectList.js
146
161
  - frameworks/uki/src/uki-more/more/view/radioButton.js
147
162
  - frameworks/uki/src/uki-more/more/view/splitTable.js
163
+ - frameworks/uki/src/uki-more/more/view/splitTable/render.js
148
164
  - frameworks/uki/src/uki-more/more/view/toggleButton.js
149
165
  - frameworks/uki/src/uki-more/more/view/treeList.js
150
166
  - frameworks/uki/src/uki-more/more/view/treeList/render.js
@@ -298,18 +314,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
298
314
  requirements:
299
315
  - - ">="
300
316
  - !ruby/object:Gem::Version
317
+ segments:
318
+ - 0
301
319
  version: "0"
302
- version:
303
320
  required_rubygems_version: !ruby/object:Gem::Requirement
304
321
  requirements:
305
322
  - - ">="
306
323
  - !ruby/object:Gem::Version
324
+ segments:
325
+ - 0
307
326
  version: "0"
308
- version:
309
327
  requirements: []
310
328
 
311
329
  rubyforge_project:
312
- rubygems_version: 1.3.5
330
+ rubygems_version: 1.3.6
313
331
  signing_key:
314
332
  specification_version: 3
315
333
  summary: uki development tools