change h5 to web
This commit is contained in:
6
doric-web/.gitignore
vendored
Normal file
6
doric-web/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
build/
|
||||
bundle/
|
||||
demo/
|
||||
.DS_Store
|
||||
package-lock.json
|
201
doric-web/LICENCE
Normal file
201
doric-web/LICENCE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [2019] [Doric.Pub]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
140
doric-web/dist/Counter.js
vendored
Normal file
140
doric-web/dist/Counter.js
vendored
Normal file
File diff suppressed because one or more lines are too long
157
doric-web/dist/DoricPlayground.js
vendored
Normal file
157
doric-web/dist/DoricPlayground.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
doric-web/dist/DoricPlayground.js.map
vendored
Normal file
1
doric-web/dist/DoricPlayground.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
671
doric-web/dist/Gobang.js
vendored
Normal file
671
doric-web/dist/Gobang.js
vendored
Normal file
@@ -0,0 +1,671 @@
|
||||
'use strict';
|
||||
|
||||
var doric = require('doric');
|
||||
|
||||
const colors = [
|
||||
"#70a1ff",
|
||||
"#7bed9f",
|
||||
"#ff6b81",
|
||||
"#a4b0be",
|
||||
"#f0932b",
|
||||
"#eb4d4b",
|
||||
"#6ab04c",
|
||||
"#e056fd",
|
||||
"#686de0",
|
||||
"#30336b",
|
||||
].map(e => doric.Color.parse(e));
|
||||
|
||||
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var State;
|
||||
(function (State) {
|
||||
State[State["Unspecified"] = 0] = "Unspecified";
|
||||
State[State["BLACK"] = 1] = "BLACK";
|
||||
State[State["WHITE"] = 2] = "WHITE";
|
||||
})(State || (State = {}));
|
||||
const count = 13;
|
||||
class AIComputer {
|
||||
constructor(matrix) {
|
||||
this.wins = [];
|
||||
this.winCount = 0;
|
||||
this.matrix = matrix;
|
||||
for (let y = 0; y < count; y++) {
|
||||
for (let x = 0; x < count - 4; x++) {
|
||||
this.wins.push([]);
|
||||
for (let k = 0; k < 5; k++) {
|
||||
this.wins[this.winCount].push({
|
||||
x: x + k,
|
||||
y,
|
||||
});
|
||||
}
|
||||
this.winCount++;
|
||||
}
|
||||
}
|
||||
for (let x = 0; x < count; x++) {
|
||||
for (let y = 0; y < count - 4; y++) {
|
||||
this.wins.push([]);
|
||||
for (let k = 0; k < 5; k++) {
|
||||
this.wins[this.winCount].push({
|
||||
x,
|
||||
y: y + k,
|
||||
});
|
||||
}
|
||||
this.winCount++;
|
||||
}
|
||||
}
|
||||
for (let x = 0; x < count - 4; x++) {
|
||||
for (let y = 0; y < count - 4; y++) {
|
||||
this.wins.push([]);
|
||||
for (let k = 0; k < 5; k++) {
|
||||
this.wins[this.winCount].push({
|
||||
x: x + k,
|
||||
y: y + k,
|
||||
});
|
||||
}
|
||||
this.winCount++;
|
||||
}
|
||||
}
|
||||
for (let x = 0; x < count - 4; x++) {
|
||||
for (let y = count - 1; y > 3; y--) {
|
||||
this.wins.push([]);
|
||||
for (let k = 0; k < 5; k++) {
|
||||
this.wins[this.winCount].push({
|
||||
x: x + k,
|
||||
y: y - k,
|
||||
});
|
||||
}
|
||||
this.winCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
get blackWins() {
|
||||
return this.wins.map((win) => {
|
||||
let idx = 0;
|
||||
for (let e of win) {
|
||||
switch (this.matrix.get(e.x + e.y * count)) {
|
||||
case State.BLACK:
|
||||
idx++;
|
||||
break;
|
||||
case State.WHITE:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
});
|
||||
}
|
||||
get whiteWins() {
|
||||
return this.wins.map((win) => {
|
||||
let idx = 0;
|
||||
for (let e of win) {
|
||||
switch (this.matrix.get(e.x + e.y * count)) {
|
||||
case State.WHITE:
|
||||
idx++;
|
||||
break;
|
||||
case State.BLACK:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
});
|
||||
}
|
||||
compute(matrix, role) {
|
||||
const myScore = new Array(matrix.length).fill(0);
|
||||
const rivalScore = new Array(matrix.length).fill(0);
|
||||
const myWins = role === State.BLACK ? this.blackWins : this.whiteWins;
|
||||
const rivalWins = role === State.BLACK ? this.whiteWins : this.blackWins;
|
||||
let max = 0;
|
||||
let retIdx = 0;
|
||||
matrix.forEach((state, idx) => {
|
||||
if (state != State.Unspecified) {
|
||||
return;
|
||||
}
|
||||
this.wins.forEach((e, winIdx) => {
|
||||
if (e.filter(e => (e.x + e.y * count) === idx).length === 0) {
|
||||
return;
|
||||
}
|
||||
switch (rivalWins[winIdx]) {
|
||||
case 1:
|
||||
rivalScore[idx] += 1;
|
||||
break;
|
||||
case 2:
|
||||
rivalScore[idx] += 10;
|
||||
break;
|
||||
case 3:
|
||||
rivalScore[idx] += 100;
|
||||
break;
|
||||
case 4:
|
||||
rivalScore[idx] += 10000;
|
||||
break;
|
||||
}
|
||||
switch (myWins[winIdx]) {
|
||||
case 1:
|
||||
myScore[idx] += 2;
|
||||
break;
|
||||
case 2:
|
||||
myScore[idx] += 20;
|
||||
break;
|
||||
case 3:
|
||||
myScore[idx] += 200;
|
||||
break;
|
||||
case 4:
|
||||
myScore[idx] += 20000;
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (rivalScore[idx] > max) {
|
||||
max = rivalScore[idx];
|
||||
retIdx = idx;
|
||||
}
|
||||
else if (rivalScore[idx] == max) {
|
||||
if (myScore[idx] > myScore[retIdx]) {
|
||||
retIdx = idx;
|
||||
}
|
||||
}
|
||||
if (myScore[idx] > max) {
|
||||
max = myScore[idx];
|
||||
retIdx = idx;
|
||||
}
|
||||
else if (myScore[idx] == max) {
|
||||
if (rivalScore[idx] > rivalScore[retIdx]) {
|
||||
retIdx = idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
return retIdx;
|
||||
}
|
||||
}
|
||||
const lineColor = doric.Color.BLACK;
|
||||
function columLine() {
|
||||
return (new doric.Stack).apply({
|
||||
layoutConfig: doric.layoutConfig().most().configWidth(doric.LayoutSpec.JUST),
|
||||
width: 1,
|
||||
backgroundColor: lineColor,
|
||||
});
|
||||
}
|
||||
function rowLine() {
|
||||
return (new doric.Stack).apply({
|
||||
layoutConfig: doric.layoutConfig().most().configHeight(doric.LayoutSpec.JUST),
|
||||
height: 1,
|
||||
backgroundColor: lineColor,
|
||||
});
|
||||
}
|
||||
function pointer(size) {
|
||||
return (new doric.Stack).apply({
|
||||
layoutConfig: doric.layoutConfig().just(),
|
||||
width: size,
|
||||
height: size,
|
||||
});
|
||||
}
|
||||
var GameMode;
|
||||
(function (GameMode) {
|
||||
GameMode[GameMode["P2P"] = 0] = "P2P";
|
||||
GameMode[GameMode["P2C"] = 1] = "P2C";
|
||||
GameMode[GameMode["C2P"] = 2] = "C2P";
|
||||
})(GameMode || (GameMode = {}));
|
||||
class GoBangVH extends doric.ViewHolder {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.gap = 0;
|
||||
this.targetZone = [];
|
||||
}
|
||||
build(root) {
|
||||
this.root = root;
|
||||
}
|
||||
actualBuild(state) {
|
||||
const boardSize = state.gap * (state.count - 1);
|
||||
const gap = state.gap;
|
||||
const borderWidth = gap;
|
||||
this.gap = state.gap;
|
||||
doric.scroller(doric.vlayout([
|
||||
doric.text({
|
||||
text: "五子棋",
|
||||
layoutConfig: doric.layoutConfig().configWidth(doric.LayoutSpec.MOST),
|
||||
textSize: 30,
|
||||
textColor: doric.Color.WHITE,
|
||||
backgroundColor: colors[0],
|
||||
textAlignment: doric.gravity().center(),
|
||||
height: 50,
|
||||
}),
|
||||
doric.stack([
|
||||
doric.stack([
|
||||
...(new Array(count - 2)).fill(0).map((_, idx) => {
|
||||
return columLine().also(v => {
|
||||
v.left = (idx + 1) * gap;
|
||||
});
|
||||
}),
|
||||
...(new Array(count - 2)).fill(0).map((_, idx) => {
|
||||
return rowLine().also(v => {
|
||||
v.top = (idx + 1) * gap;
|
||||
});
|
||||
}),
|
||||
])
|
||||
.apply({
|
||||
layoutConfig: doric.layoutConfig().just()
|
||||
.configMargin({ top: borderWidth, left: borderWidth }),
|
||||
width: boardSize,
|
||||
height: boardSize,
|
||||
border: {
|
||||
width: 1,
|
||||
color: lineColor,
|
||||
},
|
||||
}),
|
||||
...this.targetZone = (new Array(count * count)).fill(0).map((_, idx) => {
|
||||
const row = Math.floor(idx / count);
|
||||
const colum = idx % count;
|
||||
return pointer(gap).also(v => {
|
||||
v.top = (row - 0.5) * gap + borderWidth;
|
||||
v.left = (colum - 0.5) * gap + borderWidth;
|
||||
});
|
||||
}),
|
||||
]).apply({
|
||||
layoutConfig: doric.layoutConfig().just(),
|
||||
width: boardSize + 2 * borderWidth,
|
||||
height: boardSize + 2 * borderWidth,
|
||||
backgroundColor: doric.Color.parse("#E6B080"),
|
||||
}),
|
||||
this.gameMode = doric.text({
|
||||
text: "游戏模式",
|
||||
textSize: 20,
|
||||
textColor: doric.Color.WHITE,
|
||||
layoutConfig: doric.layoutConfig().most().configHeight(doric.LayoutSpec.JUST),
|
||||
height: 50,
|
||||
backgroundColor: colors[8],
|
||||
}),
|
||||
doric.hlayout([
|
||||
this.currentRole = doric.text({
|
||||
text: "当前:",
|
||||
textSize: 20,
|
||||
textColor: doric.Color.WHITE,
|
||||
layoutConfig: doric.layoutConfig().just().configWeight(1),
|
||||
height: 50,
|
||||
backgroundColor: colors[1],
|
||||
}),
|
||||
this.result = doric.text({
|
||||
text: "获胜方:",
|
||||
textSize: 20,
|
||||
textColor: doric.Color.WHITE,
|
||||
layoutConfig: doric.layoutConfig().just().configWeight(1),
|
||||
height: 50,
|
||||
backgroundColor: colors[2],
|
||||
}),
|
||||
]).apply({
|
||||
layoutConfig: doric.layoutConfig().fit().configWidth(doric.LayoutSpec.MOST),
|
||||
}),
|
||||
this.assistant = doric.text({
|
||||
text: "提示",
|
||||
textSize: 20,
|
||||
textColor: doric.Color.WHITE,
|
||||
layoutConfig: doric.layoutConfig().just().configWidth(doric.LayoutSpec.MOST),
|
||||
height: 50,
|
||||
backgroundColor: colors[3],
|
||||
}),
|
||||
])
|
||||
.apply({
|
||||
layoutConfig: doric.layoutConfig().fit(),
|
||||
backgroundColor: doric.Color.parse('#ecf0f1'),
|
||||
})).in(this.root);
|
||||
}
|
||||
}
|
||||
class GoBangVM extends doric.ViewModel {
|
||||
onAttached(state, vh) {
|
||||
if (!this.computer) {
|
||||
this.computer = new AIComputer(state.matrix);
|
||||
}
|
||||
vh.actualBuild(state);
|
||||
vh.targetZone.forEach((e, idx) => {
|
||||
e.onClick = () => {
|
||||
if (state.gameState !== 'idle') {
|
||||
return;
|
||||
}
|
||||
const zoneState = state.matrix.get(idx);
|
||||
if (zoneState === State.BLACK || zoneState === State.WHITE) {
|
||||
doric.modal(context).toast('This position had been token.');
|
||||
return;
|
||||
}
|
||||
if (state.anchor === undefined || state.anchor != idx) {
|
||||
this.updateState(it => {
|
||||
it.anchor = idx;
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.updateState(it => {
|
||||
if (it.role === 'black') {
|
||||
it.matrix.set(idx, State.BLACK);
|
||||
it.role = 'white';
|
||||
}
|
||||
else {
|
||||
it.matrix.set(idx, State.WHITE);
|
||||
it.role = 'black';
|
||||
}
|
||||
it.anchor = undefined;
|
||||
if (this.checkResult(idx)) {
|
||||
doric.modal(context).toast(`恭喜获胜方${it.role === 'white' ? "黑方" : "白方"}`);
|
||||
it.gameState = it.role === 'white' ? 'blackWin' : 'whiteWin';
|
||||
}
|
||||
else {
|
||||
if (it.role === 'black' && it.gameMode === GameMode.C2P) {
|
||||
setTimeout(() => {
|
||||
this.computeNextStep(it);
|
||||
}, 0);
|
||||
}
|
||||
else if (it.role === 'white' && it.gameMode === GameMode.P2C) {
|
||||
setTimeout(() => {
|
||||
this.computeNextStep(it);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
vh.gameMode.onClick = () => {
|
||||
doric.popover(context).show(doric.vlayout([
|
||||
...[
|
||||
{
|
||||
label: "黑方:人 白方:人",
|
||||
mode: GameMode.P2P,
|
||||
},
|
||||
{
|
||||
label: "黑方:人 白方:机",
|
||||
mode: GameMode.P2C,
|
||||
},
|
||||
{
|
||||
label: "黑方:机 白方:人",
|
||||
mode: GameMode.C2P,
|
||||
},
|
||||
].map((e) => doric.text({
|
||||
text: e.label,
|
||||
textSize: 20,
|
||||
textColor: doric.Color.WHITE,
|
||||
layoutConfig: doric.layoutConfig().just(),
|
||||
height: 50,
|
||||
width: 300,
|
||||
backgroundColor: (state.gameMode === e.mode) ? doric.Color.parse('#636e72') : doric.Color.parse('#b2bec3'),
|
||||
onClick: () => {
|
||||
this.updateState(s => {
|
||||
s.gameMode = e.mode;
|
||||
this.reset(s);
|
||||
});
|
||||
doric.popover(context).dismiss();
|
||||
},
|
||||
}))
|
||||
])
|
||||
.apply({
|
||||
layoutConfig: doric.layoutConfig().most(),
|
||||
onClick: () => {
|
||||
doric.popover(context).dismiss();
|
||||
},
|
||||
gravity: doric.Gravity.Center,
|
||||
}));
|
||||
};
|
||||
vh.result.onClick = () => {
|
||||
switch (state.gameState) {
|
||||
case "idle":
|
||||
this.updateState(state => {
|
||||
this.reset(state);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
vh.currentRole.onClick = () => {
|
||||
switch (state.gameState) {
|
||||
case "idle":
|
||||
break;
|
||||
case "blackWin":
|
||||
case "whiteWin":
|
||||
this.updateState(state => {
|
||||
this.reset(state);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
vh.assistant.onClick = () => {
|
||||
const it = this.getState();
|
||||
if (it.gameState !== 'idle') {
|
||||
return;
|
||||
}
|
||||
this.computeNextStep(it);
|
||||
if (it.gameState !== 'idle') {
|
||||
return;
|
||||
}
|
||||
if (it.role === 'black' && it.gameMode === GameMode.C2P) {
|
||||
setTimeout(() => {
|
||||
this.computeNextStep(it);
|
||||
}, 0);
|
||||
}
|
||||
else if (it.role === 'white' && it.gameMode === GameMode.P2C) {
|
||||
setTimeout(() => {
|
||||
this.computeNextStep(it);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
computeNextStep(it) {
|
||||
const tempMatrix = new Array(count * count).fill(0).map((_, idx) => {
|
||||
return it.matrix.get(idx) || State.Unspecified;
|
||||
});
|
||||
let idx = 0;
|
||||
do {
|
||||
idx = this.computer.compute(tempMatrix, it.role === 'black' ? State.BLACK : State.WHITE);
|
||||
} while (it.matrix.get(idx) === State.Unspecified);
|
||||
this.updateState(state => {
|
||||
state.matrix.set(idx, state.role === 'black' ? State.BLACK : State.WHITE);
|
||||
state.role = state.role === 'black' ? 'white' : 'black';
|
||||
if (this.checkResult(idx)) {
|
||||
doric.modal(context).toast(`恭喜获胜方${it.role === 'white' ? "黑方" : "白方"}`);
|
||||
it.gameState = it.role === 'white' ? 'blackWin' : 'whiteWin';
|
||||
}
|
||||
});
|
||||
}
|
||||
reset(it) {
|
||||
it.matrix.clear();
|
||||
it.gameState = 'idle';
|
||||
it.role = "black";
|
||||
it.anchor = undefined;
|
||||
this.computer = new AIComputer(it.matrix);
|
||||
if (it.gameMode === GameMode.C2P) {
|
||||
const idx = Math.floor(Math.random() * count) * count + Math.floor(Math.random() * count);
|
||||
it.matrix.set(idx, State.BLACK);
|
||||
it.role = 'white';
|
||||
}
|
||||
}
|
||||
onBind(state, vh) {
|
||||
vh.targetZone.forEach((v, idx) => {
|
||||
const zoneState = state.matrix.get(idx);
|
||||
switch (zoneState) {
|
||||
case State.BLACK:
|
||||
v.also(it => {
|
||||
it.backgroundColor = doric.Color.BLACK;
|
||||
it.corners = state.gap / 2;
|
||||
it.border = {
|
||||
color: doric.Color.TRANSPARENT,
|
||||
width: 0,
|
||||
};
|
||||
});
|
||||
break;
|
||||
case State.WHITE:
|
||||
v.also(it => {
|
||||
it.backgroundColor = doric.Color.WHITE;
|
||||
it.corners = state.gap / 2;
|
||||
it.border = {
|
||||
color: doric.Color.TRANSPARENT,
|
||||
width: 0,
|
||||
};
|
||||
});
|
||||
break;
|
||||
default:
|
||||
v.also(it => {
|
||||
it.backgroundColor = doric.Color.TRANSPARENT;
|
||||
it.corners = 0;
|
||||
it.border = {
|
||||
color: doric.Color.TRANSPARENT,
|
||||
width: 0,
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
if (state.anchor === idx) {
|
||||
v.also(it => {
|
||||
it.backgroundColor = doric.Color.RED.alpha(0.1);
|
||||
it.corners = 0;
|
||||
it.border = {
|
||||
color: doric.Color.RED,
|
||||
width: 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
vh.gameMode.text = `游戏模式: 黑方 ${state.gameMode === GameMode.C2P ? "机" : "人"} 白方 ${state.gameMode === GameMode.P2C ? "机" : "人"}`;
|
||||
switch (state.gameState) {
|
||||
case "idle":
|
||||
vh.result.text = "重新开始";
|
||||
vh.currentRole.text = `当前: ${(state.role === 'black') ? "黑方" : "白方"}`;
|
||||
break;
|
||||
case "blackWin":
|
||||
vh.result.text = "黑方获胜";
|
||||
vh.currentRole.text = "重新开始";
|
||||
break;
|
||||
case "whiteWin":
|
||||
vh.result.text = "白方获胜";
|
||||
vh.currentRole.text = "重新开始";
|
||||
break;
|
||||
}
|
||||
}
|
||||
checkResult(pos) {
|
||||
const matrix = this.getState().matrix;
|
||||
const state = matrix.get(pos);
|
||||
const y = Math.floor(pos / count);
|
||||
const x = pos % count;
|
||||
const getState = (x, y) => matrix.get(y * count + x);
|
||||
///Horitonzal
|
||||
{
|
||||
let left = x;
|
||||
while (left >= 1) {
|
||||
if (getState(left - 1, y) === state) {
|
||||
left -= 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let right = x;
|
||||
while (right <= count - 2) {
|
||||
if (getState(right + 1, y) === state) {
|
||||
right += 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (right - left >= 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
///Vertical
|
||||
{
|
||||
let top = y;
|
||||
while (top >= 1) {
|
||||
if (getState(x, top - 1) === state) {
|
||||
top -= 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let bottom = y;
|
||||
while (bottom <= count - 2) {
|
||||
if (getState(x, bottom + 1) === state) {
|
||||
bottom += 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bottom - top >= 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
///LT-RB
|
||||
{
|
||||
let startX = x, startY = y;
|
||||
while (startX >= 1 && startY >= 1) {
|
||||
if (getState(startX - 1, startY - 1) === state) {
|
||||
startX -= 1;
|
||||
startY -= 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let endX = x, endY = y;
|
||||
while (endX <= count - 2 && endY <= count - 2) {
|
||||
if (getState(endX + 1, endY + 1) === state) {
|
||||
endX += 1;
|
||||
endY += 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (endX - startX >= 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
///LB-RT
|
||||
{
|
||||
let startX = x, startY = y;
|
||||
while (startX >= 1 && startY <= count + 2) {
|
||||
if (getState(startX - 1, startY + 1) === state) {
|
||||
startX -= 1;
|
||||
startY += 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let endX = x, endY = y;
|
||||
while (endX <= count - 2 && endY >= 1) {
|
||||
if (getState(endX + 1, endY - 1) === state) {
|
||||
endX += 1;
|
||||
endY -= 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (endX - startX >= 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let Gobang = class Gobang extends doric.VMPanel {
|
||||
getViewModelClass() {
|
||||
return GoBangVM;
|
||||
}
|
||||
getState() {
|
||||
return {
|
||||
count,
|
||||
gap: this.getRootView().width / 14,
|
||||
role: "black",
|
||||
matrix: new Map,
|
||||
gameMode: GameMode.P2C,
|
||||
gameState: "idle"
|
||||
};
|
||||
}
|
||||
getViewHolderClass() {
|
||||
return GoBangVH;
|
||||
}
|
||||
onShow() {
|
||||
doric.navbar(context).setTitle("五子棋");
|
||||
}
|
||||
};
|
||||
Gobang = __decorate([
|
||||
Entry
|
||||
], Gobang);
|
||||
//# sourceMappingURL=Gobang.js.map
|
1
doric-web/dist/Gobang.js.map
vendored
Normal file
1
doric-web/dist/Gobang.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
110
doric-web/dist/ListDemo.js
vendored
Normal file
110
doric-web/dist/ListDemo.js
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
'use strict';
|
||||
|
||||
var doric = require('doric');
|
||||
|
||||
const colors = [
|
||||
"#70a1ff",
|
||||
"#7bed9f",
|
||||
"#ff6b81",
|
||||
"#a4b0be",
|
||||
"#f0932b",
|
||||
"#eb4d4b",
|
||||
"#6ab04c",
|
||||
"#e056fd",
|
||||
"#686de0",
|
||||
"#30336b",
|
||||
].map(e => doric.Color.parse(e));
|
||||
|
||||
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
let ListPanel = class ListPanel extends doric.Panel {
|
||||
build(rootView) {
|
||||
let offset = Math.ceil(Math.random() * colors.length);
|
||||
doric.vlayout([
|
||||
doric.list({
|
||||
itemCount: 100,
|
||||
renderItem: (idx) => {
|
||||
let counter;
|
||||
return doric.listItem(doric.hlayout([
|
||||
doric.text({
|
||||
layoutConfig: {
|
||||
widthSpec: doric.LayoutSpec.FIT,
|
||||
heightSpec: doric.LayoutSpec.JUST,
|
||||
alignment: doric.gravity().center(),
|
||||
},
|
||||
text: `Cell At Line ${idx}`,
|
||||
textAlignment: doric.gravity().center(),
|
||||
textColor: doric.Color.parse("#ffffff"),
|
||||
textSize: 20,
|
||||
height: 50,
|
||||
}),
|
||||
doric.text({
|
||||
textColor: doric.Color.parse("#ffffff"),
|
||||
textSize: 20,
|
||||
text: "",
|
||||
}).also(it => {
|
||||
counter = it;
|
||||
it.layoutConfig = {
|
||||
widthSpec: doric.LayoutSpec.FIT,
|
||||
heightSpec: doric.LayoutSpec.FIT,
|
||||
margin: {
|
||||
left: 10,
|
||||
}
|
||||
};
|
||||
})
|
||||
]).also(it => {
|
||||
it.layoutConfig = {
|
||||
widthSpec: doric.LayoutSpec.MOST,
|
||||
heightSpec: doric.LayoutSpec.FIT,
|
||||
margin: {
|
||||
bottom: 2,
|
||||
}
|
||||
};
|
||||
it.gravity = doric.gravity().center();
|
||||
it.backgroundColor = colors[(idx + offset) % colors.length];
|
||||
let clicked = 0;
|
||||
it.onClick = () => {
|
||||
counter.text = `Item Clicked ${++clicked}`;
|
||||
};
|
||||
})).also(it => {
|
||||
it.layoutConfig = {
|
||||
widthSpec: doric.LayoutSpec.MOST,
|
||||
heightSpec: doric.LayoutSpec.FIT,
|
||||
};
|
||||
it.onClick = () => {
|
||||
doric.log(`Click item at ${idx}`);
|
||||
it.height += 10;
|
||||
it.nativeChannel(context, "getWidth")().then(resolve => {
|
||||
doric.log(`resolve,${resolve}`);
|
||||
}, reject => {
|
||||
doric.log(`reject,${reject}`);
|
||||
});
|
||||
};
|
||||
});
|
||||
},
|
||||
layoutConfig: doric.layoutConfig().most().configHeight(doric.LayoutSpec.JUST),
|
||||
height: 500,
|
||||
}).also(it => {
|
||||
it.loadMore = true;
|
||||
it.onLoadMore = () => {
|
||||
it.itemCount += 100;
|
||||
};
|
||||
it.loadMoreView = doric.listItem(doric.text({
|
||||
text: "Loading",
|
||||
layoutConfig: doric.layoutConfig().most().configHeight(doric.LayoutSpec.JUST).configAlignmnet(doric.Gravity.Center),
|
||||
height: 50,
|
||||
}));
|
||||
}),
|
||||
]).apply({
|
||||
layoutConfig: doric.layoutConfig().most(),
|
||||
}).in(rootView);
|
||||
}
|
||||
};
|
||||
ListPanel = __decorate([
|
||||
Entry
|
||||
], ListPanel);
|
||||
//# sourceMappingURL=ListDemo.js.map
|
475
doric-web/dist/Snake.js
vendored
Normal file
475
doric-web/dist/Snake.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
doric-web/dist/Snake.js.map
vendored
Normal file
1
doric-web/dist/Snake.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
5041
doric-web/dist/index.js
vendored
Normal file
5041
doric-web/dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
doric-web/dist/index.js.map
vendored
Normal file
1
doric-web/dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
46
doric-web/index.html
Normal file
46
doric-web/index.html
Normal file
File diff suppressed because one or more lines are too long
4
doric-web/index.ts
Normal file
4
doric-web/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { DoricElement } from './src/DoricElement'
|
||||
import { NavigationElement } from './src/navigate/NavigationElement'
|
||||
window.customElements.define('doric-div', DoricElement);
|
||||
window.customElements.define('doric-navigation', NavigationElement);
|
29
doric-web/package.json
Normal file
29
doric-web/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "doric-web",
|
||||
"version": "0.1.0",
|
||||
"description": "Doric library for Web",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc -p .&& rollup -c"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/doric-pub/doric.git"
|
||||
},
|
||||
"author": "pengfeizhou",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/doric-pub/doric/issues"
|
||||
},
|
||||
"homepage": "https://github.com/doric-pub/doric#readme",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"doric": "file:../doric-js",
|
||||
"rollup": "^1.27.14",
|
||||
"@rollup/plugin-commonjs": "^11.0.0",
|
||||
"@rollup/plugin-json": "^4.0.1",
|
||||
"@rollup/plugin-node-resolve": "^6.0.0",
|
||||
"typescript": "^3.7.4"
|
||||
}
|
||||
}
|
48
doric-web/rollup.config.js
Normal file
48
doric-web/rollup.config.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import resolve from '@rollup/plugin-node-resolve'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import jsonPlugin from '@rollup/plugin-json'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const sandboxBundle = fs.readFileSync(path.resolve("./node_modules/doric/bundle/doric-sandbox.js"), 'utf-8')
|
||||
|
||||
const doricLibBundle = fs.readFileSync(path.resolve("./node_modules/doric/bundle/doric-lib.js"), 'utf-8')
|
||||
|
||||
const builtinScript = `
|
||||
/**++++++++SandBox++++++++*/
|
||||
${sandboxBundle}
|
||||
/**--------SandBox--------*/
|
||||
|
||||
/**++++++++Lib++++++++*/
|
||||
Reflect.apply(doric.jsRegisterModule,this,["doric",Reflect.apply(function(__module){(function(module,exports,require){
|
||||
${doricLibBundle}
|
||||
})(__module,__module.exports,doric.__require__);
|
||||
return __module.exports;
|
||||
},this,[{exports:{}}])]);
|
||||
/**--------Lib--------*/
|
||||
`
|
||||
|
||||
export default {
|
||||
input: `build/index.js`,
|
||||
output: {
|
||||
format: "iife",
|
||||
name: "index",
|
||||
file: `dist/index.js`,
|
||||
sourcemap: true,
|
||||
banner: builtinScript,
|
||||
globals: {
|
||||
doric: "doric_lib",
|
||||
'doric/src/runtime/sandbox': 'doric',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
resolve({ mainFields: ["jsnext"] }),
|
||||
commonjs(),
|
||||
jsonPlugin(),
|
||||
],
|
||||
external: ['axios', 'reflect-metadata', 'doric'],
|
||||
onwarn: function (warning) {
|
||||
if (warning.code === 'THIS_IS_UNDEFINED') { return; }
|
||||
console.warn(warning.message);
|
||||
},
|
||||
}
|
55
doric-web/src/DoricContext.ts
Normal file
55
doric-web/src/DoricContext.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { jsObtainContext, jsCallEntityMethod } from 'doric/src/runtime/sandbox'
|
||||
import { Panel } from 'doric'
|
||||
import { DoricPlugin } from "./DoricPlugin"
|
||||
import { createContext, destroyContext } from "./DoricDriver"
|
||||
import { DoricStackNode } from './shader/DoricStackNode'
|
||||
import { DoricViewNode } from './shader/DoricViewNode'
|
||||
const doricContexts: Map<string, DoricContext> = new Map
|
||||
|
||||
let __contextId__ = 0
|
||||
function getContextId() {
|
||||
return `context_${__contextId__++}`
|
||||
}
|
||||
|
||||
export function getDoricContext(contextId: string) {
|
||||
return doricContexts.get(contextId)
|
||||
}
|
||||
|
||||
export class DoricContext {
|
||||
contextId = getContextId()
|
||||
pluginInstances: Map<string, DoricPlugin> = new Map
|
||||
rootNode: DoricStackNode
|
||||
headNodes: Map<string, DoricViewNode> = new Map
|
||||
|
||||
constructor(content: string) {
|
||||
createContext(this.contextId, content)
|
||||
doricContexts.set(this.contextId, this)
|
||||
this.rootNode = new DoricStackNode(this)
|
||||
}
|
||||
|
||||
get panel() {
|
||||
return jsObtainContext(this.contextId)?.entity as Panel
|
||||
}
|
||||
|
||||
invokeEntityMethod(method: string, ...otherArgs: any) {
|
||||
const argumentsList: any = [this.contextId]
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
argumentsList.push(arguments[i])
|
||||
}
|
||||
return Reflect.apply(jsCallEntityMethod, this.panel, argumentsList)
|
||||
}
|
||||
|
||||
init(frame: {
|
||||
width: number,
|
||||
height: number,
|
||||
}, extra?: object) {
|
||||
this.invokeEntityMethod("__init__", frame, extra ? JSON.stringify(extra) : undefined)
|
||||
}
|
||||
|
||||
teardown() {
|
||||
for (let plugin of this.pluginInstances.values()) {
|
||||
plugin.onTearDown()
|
||||
}
|
||||
destroyContext(this.contextId)
|
||||
}
|
||||
}
|
147
doric-web/src/DoricDriver.ts
Normal file
147
doric-web/src/DoricDriver.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { jsCallResolve, jsCallReject, jsCallbackTimer, jsCallEntityMethod, jsReleaseContext } from 'doric/src/runtime/sandbox'
|
||||
import { acquireJSBundle, acquirePlugin } from './DoricRegistry'
|
||||
import { getDoricContext } from './DoricContext'
|
||||
import { DoricPlugin } from './DoricPlugin'
|
||||
|
||||
function getScriptId(contextId: string) {
|
||||
return `__doric_script_${contextId}`
|
||||
}
|
||||
|
||||
const originSetTimeout = window.setTimeout
|
||||
const originClearTimeout = window.clearTimeout
|
||||
const originSetInterval = window.setInterval
|
||||
const originClearInterval = window.clearInterval
|
||||
|
||||
const timers: Map<number, { handleId: number, repeat: boolean }> = new Map
|
||||
|
||||
export function injectGlobalObject(name: string, value: any) {
|
||||
Reflect.set(window, name, value, window)
|
||||
}
|
||||
|
||||
export function loadJS(contextId: string, script: string) {
|
||||
const scriptElement = document.createElement('script')
|
||||
scriptElement.text = script
|
||||
scriptElement.id = getScriptId(contextId)
|
||||
document.body.appendChild(scriptElement)
|
||||
}
|
||||
|
||||
function packageModuleScript(name: string, content: string) {
|
||||
return `Reflect.apply(doric.jsRegisterModule,this,[${name},Reflect.apply(function(__module){(function(module,exports,require,setTimeout,setInterval,clearTimeout,clearInterval){
|
||||
${content}
|
||||
})(__module,__module.exports,doric.__require__,doricSetTimeout,doricSetInterval,doricClearTimeout,doricClearInterval);
|
||||
return __module.exports;},this,[{exports:{}}])])`
|
||||
}
|
||||
|
||||
function packageCreateContext(contextId: string, content: string) {
|
||||
return `//@ sourceURL=contextId_${contextId}.js
|
||||
Reflect.apply(function(doric,context,Entry,require,exports,setTimeout,setInterval,clearTimeout,clearInterval){
|
||||
${content}
|
||||
},doric.jsObtainContext("${contextId}"),[undefined,doric.jsObtainContext("${contextId}"),doric.jsObtainEntry("${contextId}"),doric.__require__,{},doricSetTimeout,doricSetInterval,doricClearTimeout,doricClearInterval])`
|
||||
}
|
||||
|
||||
function initDoric() {
|
||||
injectGlobalObject("Environment", {
|
||||
platform: "h5"
|
||||
})
|
||||
|
||||
injectGlobalObject("nativeEmpty", () => undefined)
|
||||
|
||||
injectGlobalObject('nativeLog', (type: 'd' | 'w' | 'e', message: string) => {
|
||||
switch (type) {
|
||||
case 'd':
|
||||
console.log(message)
|
||||
break
|
||||
case 'w':
|
||||
console.warn(message)
|
||||
break
|
||||
case 'e':
|
||||
console.error(message)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
injectGlobalObject('nativeRequire', (moduleName: string) => {
|
||||
const bundle = acquireJSBundle(moduleName)
|
||||
if (bundle === undefined || bundle.length === 0) {
|
||||
console.log(`Cannot require JS Bundle :${moduleName}`)
|
||||
return false
|
||||
} else {
|
||||
loadJS(moduleName, packageModuleScript(moduleName, packageModuleScript(name, bundle)))
|
||||
return true
|
||||
}
|
||||
})
|
||||
injectGlobalObject('nativeBridge', (contextId: string, namespace: string, method: string, callbackId: string, args?: any) => {
|
||||
const pluginClass = acquirePlugin(namespace)
|
||||
const doricContext = getDoricContext(contextId)
|
||||
if (pluginClass === undefined) {
|
||||
console.error(`Cannot find Plugin:${namespace}`)
|
||||
return false
|
||||
}
|
||||
if (doricContext === undefined) {
|
||||
console.error(`Cannot find Doric Context:${contextId}`)
|
||||
return false
|
||||
}
|
||||
let plugin = doricContext.pluginInstances.get(namespace)
|
||||
if (plugin === undefined) {
|
||||
plugin = new pluginClass(doricContext) as DoricPlugin
|
||||
doricContext.pluginInstances.set(namespace, plugin)
|
||||
}
|
||||
if (!Reflect.has(plugin, method)) {
|
||||
console.error(`Cannot find Method:${method} in plugin ${namespace}`)
|
||||
return false
|
||||
}
|
||||
const pluginMethod = Reflect.get(plugin, method, plugin)
|
||||
if (typeof pluginMethod !== 'function') {
|
||||
console.error(`Plugin ${namespace}'s property ${method}'s type is ${typeof pluginMethod} not function,`)
|
||||
}
|
||||
const ret = Reflect.apply(pluginMethod, plugin, [args])
|
||||
if (ret instanceof Promise) {
|
||||
ret.then(
|
||||
e => {
|
||||
jsCallResolve(contextId, callbackId, e)
|
||||
},
|
||||
e => {
|
||||
jsCallReject(contextId, callbackId, e)
|
||||
})
|
||||
} else if (ret !== undefined) {
|
||||
jsCallResolve(contextId, callbackId, ret)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
injectGlobalObject('nativeSetTimer', (timerId: number, time: number, repeat: boolean) => {
|
||||
if (repeat) {
|
||||
const handleId = originSetInterval(() => {
|
||||
jsCallbackTimer(timerId)
|
||||
}, time)
|
||||
timers.set(timerId, { handleId, repeat })
|
||||
} else {
|
||||
const handleId = originSetTimeout(() => {
|
||||
jsCallbackTimer(timerId)
|
||||
}, time)
|
||||
timers.set(timerId, { handleId, repeat })
|
||||
}
|
||||
})
|
||||
injectGlobalObject('nativeClearTimer', (timerId: number) => {
|
||||
const timerInfo = timers.get(timerId)
|
||||
if (timerInfo) {
|
||||
if (timerInfo.repeat) {
|
||||
originClearInterval(timerInfo.handleId)
|
||||
} else {
|
||||
originClearTimeout(timerInfo.handleId)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function createContext(contextId: string, content: string) {
|
||||
loadJS(contextId, packageCreateContext(contextId, content))
|
||||
}
|
||||
export function destroyContext(contextId: string) {
|
||||
jsReleaseContext(contextId)
|
||||
const scriptElement = document.getElementById(getScriptId(contextId))
|
||||
if (scriptElement) {
|
||||
document.body.removeChild(scriptElement)
|
||||
}
|
||||
}
|
||||
initDoric()
|
59
doric-web/src/DoricElement.ts
Normal file
59
doric-web/src/DoricElement.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import axios from 'axios'
|
||||
import { DoricContext } from './DoricContext'
|
||||
|
||||
|
||||
export class DoricElement extends HTMLElement {
|
||||
context?: DoricContext
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
get src() {
|
||||
return this.getAttribute('src') as string
|
||||
}
|
||||
|
||||
get alias() {
|
||||
return this.getAttribute('alias') as string
|
||||
}
|
||||
set src(v: string) {
|
||||
this.setAttribute('src', v)
|
||||
}
|
||||
set alias(v: string) {
|
||||
this.setAttribute('alias', v)
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.src && this.context === undefined) {
|
||||
axios.get<string>(this.src).then(result => {
|
||||
this.load(result.data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
}
|
||||
|
||||
adoptedCallback() {
|
||||
|
||||
}
|
||||
|
||||
attributeChangedCallback() {
|
||||
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
this.context?.teardown()
|
||||
}
|
||||
|
||||
load(content: string) {
|
||||
this.context = new DoricContext(content)
|
||||
const divElement = document.createElement('div')
|
||||
divElement.style.position = 'relative'
|
||||
divElement.style.height = '100%'
|
||||
this.append(divElement)
|
||||
this.context.rootNode.view = divElement
|
||||
this.context.init({
|
||||
width: divElement.offsetWidth,
|
||||
height: divElement.offsetHeight,
|
||||
})
|
||||
}
|
||||
}
|
12
doric-web/src/DoricPlugin.ts
Normal file
12
doric-web/src/DoricPlugin.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { DoricContext } from "./DoricContext"
|
||||
|
||||
export type DoricPluginClass = { new(...args: any[]): {} }
|
||||
export class DoricPlugin {
|
||||
context: DoricContext
|
||||
constructor(context: DoricContext) {
|
||||
this.context = context
|
||||
}
|
||||
onTearDown() {
|
||||
|
||||
}
|
||||
}
|
63
doric-web/src/DoricRegistry.ts
Normal file
63
doric-web/src/DoricRegistry.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { DoricPluginClass } from "./DoricPlugin"
|
||||
import { ShaderPlugin } from "./plugins/ShaderPlugin"
|
||||
import { DoricViewNodeClass } from "./shader/DoricViewNode"
|
||||
import { DoricStackNode } from "./shader/DoricStackNode"
|
||||
import { DoricVLayoutNode } from './shader/DoricVLayoutNode'
|
||||
import { DoricHLayoutNode } from './shader/DoricHLayoutNode'
|
||||
import { DoricTextNode } from "./shader/DoricTextNode"
|
||||
import { DoricImageNode } from "./shader/DoricImageNode"
|
||||
import { DoricScrollerNode } from "./shader/DoricScrollerNode"
|
||||
import { ModalPlugin } from './plugins/ModalPlugin'
|
||||
import { StoragePlugin } from "./plugins/StoragePlugin"
|
||||
import { NavigatorPlugin } from "./navigate/NavigatorPlugin"
|
||||
import { PopoverPlugin } from './plugins/PopoverPlugin'
|
||||
import { DoricListItemNode } from "./shader/DoricListItemNode"
|
||||
import { DoricListNode } from "./shader/DoricListNode"
|
||||
|
||||
const bundles: Map<string, string> = new Map
|
||||
|
||||
const plugins: Map<string, DoricPluginClass> = new Map
|
||||
|
||||
const nodes: Map<string, DoricViewNodeClass> = new Map
|
||||
|
||||
|
||||
export function acquireJSBundle(name: string) {
|
||||
return bundles.get(name)
|
||||
}
|
||||
|
||||
export function registerJSBundle(name: string, bundle: string) {
|
||||
bundles.set(name, bundle)
|
||||
}
|
||||
|
||||
export function registerPlugin(name: string, plugin: DoricPluginClass) {
|
||||
plugins.set(name, plugin)
|
||||
}
|
||||
|
||||
export function acquirePlugin(name: string) {
|
||||
return plugins.get(name)
|
||||
}
|
||||
|
||||
export function registerViewNode(name: string, node: DoricViewNodeClass) {
|
||||
nodes.set(name, node)
|
||||
}
|
||||
|
||||
export function acquireViewNode(name: string) {
|
||||
return nodes.get(name)
|
||||
}
|
||||
|
||||
|
||||
|
||||
registerPlugin('shader', ShaderPlugin)
|
||||
registerPlugin('modal', ModalPlugin)
|
||||
registerPlugin('storage', StoragePlugin)
|
||||
registerPlugin('navigator', NavigatorPlugin)
|
||||
registerPlugin('popover', PopoverPlugin)
|
||||
|
||||
registerViewNode('Stack', DoricStackNode)
|
||||
registerViewNode('VLayout', DoricVLayoutNode)
|
||||
registerViewNode('HLayout', DoricHLayoutNode)
|
||||
registerViewNode('Text', DoricTextNode)
|
||||
registerViewNode('Image', DoricImageNode)
|
||||
registerViewNode('Scroller', DoricScrollerNode)
|
||||
registerViewNode('ListItem', DoricListItemNode)
|
||||
registerViewNode('List', DoricListNode)
|
36
doric-web/src/navigate/NavigationElement.ts
Normal file
36
doric-web/src/navigate/NavigationElement.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { DoricElement } from "../DoricElement"
|
||||
|
||||
export class NavigationElement extends HTMLElement {
|
||||
|
||||
elementStack: DoricElement[] = []
|
||||
|
||||
get currentNode() {
|
||||
for (let i = 0; i < this.childNodes.length; i++) {
|
||||
if (this.childNodes[i] instanceof DoricElement) {
|
||||
return this.childNodes[i] as DoricElement
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
push(element: DoricElement) {
|
||||
const currentNode = this.currentNode
|
||||
if (currentNode) {
|
||||
this.elementStack.push(currentNode)
|
||||
this.replaceChild(element, currentNode)
|
||||
} else {
|
||||
this.appendChild(element)
|
||||
}
|
||||
}
|
||||
|
||||
pop() {
|
||||
const lastElement = this.elementStack.pop()
|
||||
const currentNode = this.currentNode
|
||||
if (lastElement && currentNode) {
|
||||
this.replaceChild(lastElement, currentNode)
|
||||
currentNode.onDestroy()
|
||||
} else {
|
||||
window.history.back()
|
||||
}
|
||||
}
|
||||
}
|
34
doric-web/src/navigate/NavigatorPlugin.ts
Normal file
34
doric-web/src/navigate/NavigatorPlugin.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DoricPlugin } from "../DoricPlugin";
|
||||
import { DoricElement } from "../DoricElement";
|
||||
import { NavigationElement } from "./NavigationElement";
|
||||
|
||||
export class NavigatorPlugin extends DoricPlugin {
|
||||
navigation: NavigationElement | undefined = document.getElementsByTagName('doric-navigation')[0] as (NavigationElement | undefined)
|
||||
|
||||
push(args: {
|
||||
scheme: string,
|
||||
config?: {
|
||||
alias?: string,
|
||||
extra?: string,
|
||||
}
|
||||
}) {
|
||||
if (this.navigation) {
|
||||
const div = new DoricElement
|
||||
div.src = args.scheme
|
||||
div.alias = args.config?.alias || args.scheme
|
||||
this.navigation.push(div)
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
return Promise.reject('Not implemented')
|
||||
}
|
||||
}
|
||||
|
||||
pop() {
|
||||
if (this.navigation) {
|
||||
this.navigation.pop()
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
return Promise.reject('Not implemented')
|
||||
}
|
||||
}
|
||||
}
|
72
doric-web/src/plugins/ModalPlugin.ts
Normal file
72
doric-web/src/plugins/ModalPlugin.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { DoricPlugin } from '../DoricPlugin'
|
||||
import { TOP, CENTER_Y, BOTTOM, toPixelString } from '../shader/DoricViewNode'
|
||||
|
||||
export class ModalPlugin extends DoricPlugin {
|
||||
toast(args: {
|
||||
msg?: string,
|
||||
gravity?: number
|
||||
}) {
|
||||
const toastElement = document.createElement('div')
|
||||
toastElement.style.position = "absolute"
|
||||
toastElement.style.textAlign = "center"
|
||||
toastElement.style.width = "100%"
|
||||
|
||||
const textElement = document.createElement('span')
|
||||
textElement.innerText = args.msg || ""
|
||||
textElement.style.backgroundColor = "#777777"
|
||||
textElement.style.color = "white"
|
||||
textElement.style.paddingLeft = '20px'
|
||||
textElement.style.paddingRight = '20px'
|
||||
textElement.style.paddingTop = '10px'
|
||||
textElement.style.paddingBottom = '10px'
|
||||
toastElement.appendChild(textElement)
|
||||
document.body.appendChild(toastElement)
|
||||
const gravity = args.gravity || BOTTOM
|
||||
if ((gravity & TOP) == TOP) {
|
||||
toastElement.style.top = toPixelString(30)
|
||||
} else if ((gravity & BOTTOM) == BOTTOM) {
|
||||
toastElement.style.bottom = toPixelString(30)
|
||||
} else if ((gravity & CENTER_Y) == CENTER_Y) {
|
||||
toastElement.style.top = toPixelString(document.body.offsetHeight / 2 - toastElement.offsetHeight / 2)
|
||||
}
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(toastElement)
|
||||
}, 2000)
|
||||
return Promise.resolve()
|
||||
}
|
||||
alert(args: {
|
||||
title?: string,
|
||||
msg?: string,
|
||||
okLabel?: string,
|
||||
}) {
|
||||
window.alert(args.msg || "")
|
||||
return Promise.resolve()
|
||||
}
|
||||
confirm(args: {
|
||||
title?: string,
|
||||
msg?: string,
|
||||
okLabel?: string,
|
||||
cancelLabel?: string,
|
||||
}) {
|
||||
if (window.confirm(args.msg || "")) {
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
return Promise.reject()
|
||||
}
|
||||
}
|
||||
prompt(args: {
|
||||
title?: string,
|
||||
msg?: string,
|
||||
okLabel?: string,
|
||||
cancelLabel?: string,
|
||||
defaultText?: string
|
||||
text?: string
|
||||
}) {
|
||||
const result = window.prompt(args.msg || "", args.defaultText)
|
||||
if (result) {
|
||||
return Promise.resolve(result)
|
||||
} else {
|
||||
return Promise.reject(result)
|
||||
}
|
||||
}
|
||||
}
|
59
doric-web/src/plugins/PopoverPlugin.ts
Normal file
59
doric-web/src/plugins/PopoverPlugin.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { DoricPlugin } from '../DoricPlugin'
|
||||
import { DVModel, DoricViewNode } from '../shader/DoricViewNode';
|
||||
import { DoricContext } from '../DoricContext';
|
||||
|
||||
export class PopoverPlugin extends DoricPlugin {
|
||||
fullScreen = document.createElement('div')
|
||||
constructor(context: DoricContext) {
|
||||
super(context)
|
||||
this.fullScreen.id = `PopOver__${context.contextId}`
|
||||
this.fullScreen.style.position = 'fixed'
|
||||
this.fullScreen.style.top = '0px'
|
||||
this.fullScreen.style.width = "100%"
|
||||
this.fullScreen.style.height = "100%"
|
||||
}
|
||||
|
||||
show(model: DVModel) {
|
||||
const viewNode = DoricViewNode.create(this.context, model.type)
|
||||
if (viewNode === undefined) {
|
||||
return Promise.reject(`Cannot create ViewNode for ${model.type}`)
|
||||
}
|
||||
viewNode.viewId = model.id
|
||||
viewNode.init()
|
||||
viewNode.blend(model.props)
|
||||
this.fullScreen.appendChild(viewNode.view)
|
||||
this.context.headNodes.set(model.id, viewNode)
|
||||
if (!document.body.contains(this.fullScreen)) {
|
||||
document.body.appendChild(this.fullScreen)
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
dismiss(args?: { id: string }) {
|
||||
if (args) {
|
||||
const viewNode = this.context.headNodes.get(args.id)
|
||||
if (viewNode) {
|
||||
this.fullScreen.removeChild(viewNode.view)
|
||||
}
|
||||
if (this.context.headNodes.size === 0) {
|
||||
document.body.removeChild(this.fullScreen)
|
||||
}
|
||||
} else {
|
||||
this.dismissAll()
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
dismissAll() {
|
||||
for (let node of this.context.headNodes.values()) {
|
||||
this.context.headNodes.delete(node.viewId)
|
||||
this.fullScreen.removeChild(node.view)
|
||||
}
|
||||
if (document.body.contains(this.fullScreen)) {
|
||||
document.body.removeChild(this.fullScreen)
|
||||
}
|
||||
}
|
||||
onTearDown() {
|
||||
super.onTearDown()
|
||||
this.dismissAll()
|
||||
}
|
||||
}
|
20
doric-web/src/plugins/ShaderPlugin.ts
Normal file
20
doric-web/src/plugins/ShaderPlugin.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { DoricPlugin } from "../DoricPlugin";
|
||||
import { DVModel } from "../shader/DoricViewNode";
|
||||
|
||||
export class ShaderPlugin extends DoricPlugin {
|
||||
render(ret: DVModel) {
|
||||
if (this.context.rootNode.viewId?.length > 0) {
|
||||
if (this.context.rootNode.viewId === ret.id) {
|
||||
this.context.rootNode.blend(ret.props)
|
||||
} else {
|
||||
const viewNode = this.context.headNodes.get(ret.id)
|
||||
if (viewNode) {
|
||||
viewNode.blend(ret.props)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.context.rootNode.viewId = ret.id
|
||||
this.context.rootNode.blend(ret.props)
|
||||
}
|
||||
}
|
||||
}
|
41
doric-web/src/plugins/StoragePlugin.ts
Normal file
41
doric-web/src/plugins/StoragePlugin.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { DoricPlugin } from "../DoricPlugin";
|
||||
|
||||
export class StoragePlugin extends DoricPlugin {
|
||||
setItem(args: {
|
||||
zone?: string,
|
||||
key: string,
|
||||
value: string
|
||||
}) {
|
||||
localStorage.setItem(`${args.zone}_${args.key}`, args.value)
|
||||
return Promise.resolve()
|
||||
}
|
||||
getItem(args: {
|
||||
zone?: string,
|
||||
key: string,
|
||||
}) {
|
||||
return Promise.resolve(localStorage.getItem(`${args.zone}_${args.key}`))
|
||||
}
|
||||
|
||||
remove(args: {
|
||||
zone?: string,
|
||||
key: string,
|
||||
}) {
|
||||
localStorage.removeItem(`${args.zone}_${args.key}`)
|
||||
return Promise.resolve()
|
||||
}
|
||||
clear(args: {
|
||||
zone: string,
|
||||
}) {
|
||||
let removingKeys = []
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i)
|
||||
if (key && key.startsWith(`${args.zone}_`)) {
|
||||
removingKeys.push(key)
|
||||
}
|
||||
}
|
||||
removingKeys.forEach(e => {
|
||||
localStorage.removeItem(e)
|
||||
})
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
60
doric-web/src/shader/DoricHLayoutNode.ts
Normal file
60
doric-web/src/shader/DoricHLayoutNode.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { DoricGroupViewNode, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString } from "./DoricViewNode";
|
||||
|
||||
export class DoricHLayoutNode extends DoricGroupViewNode {
|
||||
space = 0
|
||||
gravity = 0
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.display = "flex"
|
||||
ret.style.flexDirection = "row"
|
||||
ret.style.flexWrap = "nowrap"
|
||||
return ret
|
||||
}
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'space') {
|
||||
this.space = prop
|
||||
} else if (propName === 'gravity') {
|
||||
this.gravity = prop
|
||||
this.gravity = prop
|
||||
if ((this.gravity & LEFT) === LEFT) {
|
||||
this.view.style.justifyContent = "flex-start"
|
||||
} else if ((this.gravity & RIGHT) === RIGHT) {
|
||||
this.view.style.justifyContent = "flex-end"
|
||||
} else if ((this.gravity & CENTER_X) === CENTER_X) {
|
||||
this.view.style.justifyContent = "center"
|
||||
}
|
||||
if ((this.gravity & TOP) === TOP) {
|
||||
this.view.style.alignItems = "flex-start"
|
||||
} else if ((this.gravity & BOTTOM) === BOTTOM) {
|
||||
this.view.style.alignItems = "flex-end"
|
||||
} else if ((this.gravity & CENTER_Y) === CENTER_Y) {
|
||||
this.view.style.alignItems = "center"
|
||||
}
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
layout() {
|
||||
super.layout()
|
||||
this.childNodes.forEach((e, idx) => {
|
||||
e.view.style.flexShrink = "0"
|
||||
if (e.layoutConfig?.weight) {
|
||||
e.view.style.flex = `${e.layoutConfig?.weight}`
|
||||
}
|
||||
e.view.style.marginLeft = toPixelString(e.layoutConfig?.margin?.left || 0)
|
||||
e.view.style.marginRight = toPixelString(
|
||||
(idx === this.childNodes.length - 1) ? 0 : this.space
|
||||
+ (e.layoutConfig?.margin?.right || 0))
|
||||
e.view.style.marginTop = toPixelString(e.layoutConfig?.margin?.top || 0)
|
||||
e.view.style.marginBottom = toPixelString(e.layoutConfig?.margin?.bottom || 0)
|
||||
if ((e.layoutConfig.alignment & TOP) === TOP) {
|
||||
e.view.style.alignSelf = "flex-start"
|
||||
} else if ((e.layoutConfig.alignment & BOTTOM) === BOTTOM) {
|
||||
e.view.style.alignSelf = "flex-end"
|
||||
} else if ((e.layoutConfig.alignment & CENTER_Y) === CENTER_Y) {
|
||||
e.view.style.alignSelf = "center"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
59
doric-web/src/shader/DoricImageNode.ts
Normal file
59
doric-web/src/shader/DoricImageNode.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { DoricViewNode, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString, toRGBAString } from "./DoricViewNode";
|
||||
|
||||
enum ScaleType {
|
||||
ScaleToFill = 0,
|
||||
ScaleAspectFit,
|
||||
ScaleAspectFill,
|
||||
}
|
||||
|
||||
export class DoricImageNode extends DoricViewNode {
|
||||
|
||||
build(): HTMLElement {
|
||||
const ret = document.createElement('img')
|
||||
ret.style.objectFit = "fill"
|
||||
return ret
|
||||
}
|
||||
|
||||
blendProps(v: HTMLImageElement, propName: string, prop: any) {
|
||||
switch (propName) {
|
||||
case 'imageUrl':
|
||||
v.setAttribute('src', prop)
|
||||
break
|
||||
case 'imageBase64':
|
||||
v.setAttribute('src', prop)
|
||||
break
|
||||
case 'loadCallback':
|
||||
v.onload = () => {
|
||||
this.callJSResponse(prop, {
|
||||
width: v.width,
|
||||
height: v.height
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'scaleType':
|
||||
switch (prop) {
|
||||
case ScaleType.ScaleToFill:
|
||||
v.style.objectFit = "fill"
|
||||
break
|
||||
case ScaleType.ScaleAspectFit:
|
||||
v.style.objectFit = "contain"
|
||||
break
|
||||
case ScaleType.ScaleAspectFill:
|
||||
v.style.objectFit = "cover"
|
||||
break
|
||||
}
|
||||
break
|
||||
case 'isBlur':
|
||||
if (prop) {
|
||||
v.style.filter = 'blur(8px)'
|
||||
} else {
|
||||
v.style.filter = ''
|
||||
}
|
||||
break
|
||||
default:
|
||||
super.blendProps(v, propName, prop)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
5
doric-web/src/shader/DoricListItemNode.ts
Normal file
5
doric-web/src/shader/DoricListItemNode.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { DoricStackNode } from "./DoricStackNode";
|
||||
|
||||
export class DoricListItemNode extends DoricStackNode {
|
||||
|
||||
}
|
109
doric-web/src/shader/DoricListNode.ts
Normal file
109
doric-web/src/shader/DoricListNode.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { DoricSuperNode, DVModel, DoricViewNode } from "./DoricViewNode";
|
||||
import { DoricListItemNode } from "./DoricListItemNode";
|
||||
|
||||
export class DoricListNode extends DoricSuperNode {
|
||||
itemCount = 0
|
||||
renderItemFuncId?: string
|
||||
onLoadMoreFuncId?: string
|
||||
loadMoreViewId?: string
|
||||
batchCount = 15
|
||||
loadMore = false
|
||||
childNodes: DoricListItemNode[] = []
|
||||
loadMoreViewNode?: DoricListItemNode
|
||||
blendProps(v: HTMLParagraphElement, propName: string, prop: any) {
|
||||
switch (propName) {
|
||||
case "itemCount":
|
||||
this.itemCount = prop as number
|
||||
break
|
||||
case "renderItem":
|
||||
this.reset()
|
||||
this.renderItemFuncId = prop as string
|
||||
break
|
||||
case "onLoadMore":
|
||||
this.onLoadMoreFuncId = prop as string
|
||||
break
|
||||
case "loadMoreView":
|
||||
this.loadMoreViewId = prop as string
|
||||
break
|
||||
case "batchCount":
|
||||
this.batchCount = prop as number
|
||||
break
|
||||
case "loadMore":
|
||||
this.loadMore = prop as boolean
|
||||
break
|
||||
default:
|
||||
super.blendProps(v, propName, prop)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
while (this.view.lastElementChild) {
|
||||
this.view.removeChild(this.view.lastElementChild)
|
||||
}
|
||||
}
|
||||
|
||||
onBlended() {
|
||||
super.onBlended()
|
||||
if (this.childNodes.length !== this.itemCount) {
|
||||
const ret = this.callJSResponse("renderBunchedItems", this.childNodes.length, this.itemCount) as DVModel[]
|
||||
this.childNodes = this.childNodes.concat(ret.map(e => {
|
||||
const viewNode = DoricViewNode.create(this.context, e.type) as DoricListItemNode
|
||||
viewNode.viewId = e.id
|
||||
viewNode.init(this)
|
||||
viewNode.blend(e.props)
|
||||
this.view.appendChild(viewNode.view)
|
||||
return viewNode
|
||||
}))
|
||||
}
|
||||
if (this.loadMoreViewNode && this.view.contains(this.loadMoreViewNode.view)) {
|
||||
this.view.removeChild(this.loadMoreViewNode.view)
|
||||
}
|
||||
if (this.loadMore) {
|
||||
if (!this.loadMoreViewNode) {
|
||||
const loadMoreViewModel = this.getSubModel(this.loadMoreViewId || "")
|
||||
if (loadMoreViewModel) {
|
||||
this.loadMoreViewNode = DoricViewNode.create(this.context, loadMoreViewModel.type) as DoricListItemNode
|
||||
this.loadMoreViewNode.viewId = loadMoreViewModel.id
|
||||
this.loadMoreViewNode.init(this)
|
||||
this.loadMoreViewNode.blend(loadMoreViewModel.props)
|
||||
}
|
||||
}
|
||||
if (this.loadMoreViewNode) {
|
||||
this.view.appendChild(this.loadMoreViewNode.view)
|
||||
}
|
||||
}
|
||||
}
|
||||
blendSubNode(model: DVModel) {
|
||||
const viewNode = this.getSubNodeById(model.id)
|
||||
if (viewNode) {
|
||||
viewNode.blend(model.props)
|
||||
}
|
||||
}
|
||||
|
||||
getSubNodeById(viewId: string) {
|
||||
if (viewId === this.loadMoreViewId) {
|
||||
return this.loadMoreViewNode
|
||||
}
|
||||
return this.childNodes.filter(e => e.viewId === viewId)[0]
|
||||
}
|
||||
|
||||
onScrollToEnd() {
|
||||
if (this.loadMore && this.onLoadMoreFuncId) {
|
||||
this.callJSResponse(this.onLoadMoreFuncId)
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.overflow = "scroll"
|
||||
ret.addEventListener("scroll", () => {
|
||||
if (this.loadMore) {
|
||||
if (ret.scrollTop + ret.offsetHeight === ret.scrollHeight) {
|
||||
this.onScrollToEnd()
|
||||
}
|
||||
}
|
||||
})
|
||||
return ret
|
||||
}
|
||||
}
|
18
doric-web/src/shader/DoricRefreshableNode.ts
Normal file
18
doric-web/src/shader/DoricRefreshableNode.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { DoricSuperNode, DVModel } from "./DoricViewNode";
|
||||
|
||||
export class DoricRefreshableNode extends DoricSuperNode {
|
||||
blendSubNode(model: DVModel) {
|
||||
|
||||
}
|
||||
|
||||
getSubNodeById(viewId: string) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
}
|
69
doric-web/src/shader/DoricScrollerNode.ts
Normal file
69
doric-web/src/shader/DoricScrollerNode.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString, DoricSuperNode, DVModel, DoricViewNode } from "./DoricViewNode";
|
||||
|
||||
export class DoricScrollerNode extends DoricSuperNode {
|
||||
|
||||
|
||||
childViewId: string = ""
|
||||
childNode?: DoricViewNode
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.overflow = "scroll"
|
||||
return ret
|
||||
}
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'content') {
|
||||
this.childViewId = prop
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
blendSubNode(model: DVModel): void {
|
||||
this.childNode?.blend(model.props)
|
||||
}
|
||||
getSubNodeById(viewId: string) {
|
||||
return viewId === this.childViewId ? this.childNode : undefined
|
||||
}
|
||||
|
||||
onBlended() {
|
||||
super.onBlended()
|
||||
const model = this.getSubModel(this.childViewId)
|
||||
if (model === undefined) {
|
||||
return
|
||||
}
|
||||
if (this.childNode) {
|
||||
if (this.childNode.viewId === this.childViewId) {
|
||||
///skip
|
||||
} else {
|
||||
if (this.reusable && this.childNode.viewType === model.type) {
|
||||
this.childNode.viewId = model.id
|
||||
this.childNode.blend(model.props)
|
||||
} else {
|
||||
this.view.removeChild(this.childNode.view)
|
||||
const childNode = DoricViewNode.create(this.context, model.type)
|
||||
if (childNode === undefined) {
|
||||
return
|
||||
}
|
||||
childNode.viewId = model.id
|
||||
childNode.init(this)
|
||||
childNode.blend(model.props)
|
||||
this.view.appendChild(childNode.view)
|
||||
this.childNode = childNode
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const childNode = DoricViewNode.create(this.context, model.type)
|
||||
if (childNode === undefined) {
|
||||
return
|
||||
}
|
||||
childNode.viewId = model.id
|
||||
childNode.init(this)
|
||||
childNode.blend(model.props)
|
||||
this.view.appendChild(childNode.view)
|
||||
this.childNode = childNode
|
||||
}
|
||||
}
|
||||
|
||||
layout() {
|
||||
super.layout()
|
||||
}
|
||||
}
|
56
doric-web/src/shader/DoricStackNode.ts
Normal file
56
doric-web/src/shader/DoricStackNode.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { DoricGroupViewNode, LayoutSpec, FrameSize, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString } from "./DoricViewNode";
|
||||
|
||||
export class DoricStackNode extends DoricGroupViewNode {
|
||||
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.position = "relative"
|
||||
return ret
|
||||
}
|
||||
|
||||
layout() {
|
||||
super.layout()
|
||||
Promise.resolve().then(_ => {
|
||||
this.configSize()
|
||||
this.configOffset()
|
||||
})
|
||||
}
|
||||
|
||||
configSize() {
|
||||
if (this.layoutConfig.widthSpec === LayoutSpec.WRAP_CONTENT) {
|
||||
const width = this.childNodes.reduce((prev, current) => {
|
||||
return Math.max(prev, current.view.offsetWidth)
|
||||
}, 0)
|
||||
this.view.style.width = toPixelString(width)
|
||||
}
|
||||
if (this.layoutConfig.heightSpec === LayoutSpec.WRAP_CONTENT) {
|
||||
const height = this.childNodes.reduce((prev, current) => {
|
||||
return Math.max(prev, current.view.offsetHeight)
|
||||
}, 0)
|
||||
this.view.style.height = toPixelString(height)
|
||||
}
|
||||
}
|
||||
|
||||
configOffset() {
|
||||
this.childNodes.forEach(e => {
|
||||
e.view.style.position = "absolute"
|
||||
e.view.style.left = toPixelString(e.offsetX + this.paddingLeft)
|
||||
e.view.style.top = toPixelString(e.offsetY + this.paddingTop)
|
||||
const gravity = e.layoutConfig.alignment
|
||||
if ((gravity & LEFT) === LEFT) {
|
||||
e.view.style.left = toPixelString(0)
|
||||
} else if ((gravity & RIGHT) === RIGHT) {
|
||||
e.view.style.left = toPixelString(this.view.offsetWidth - e.view.offsetWidth)
|
||||
} else if ((gravity & CENTER_X) === CENTER_X) {
|
||||
e.view.style.left = toPixelString(this.view.offsetWidth / 2 - e.view.offsetWidth / 2)
|
||||
}
|
||||
if ((gravity & TOP) === TOP) {
|
||||
e.view.style.top = toPixelString(0)
|
||||
} else if ((gravity & BOTTOM) === BOTTOM) {
|
||||
e.view.style.top = toPixelString(this.view.offsetHeight - e.view.offsetHeight)
|
||||
} else if ((gravity & CENTER_Y) === CENTER_Y) {
|
||||
e.view.style.top = toPixelString(this.view.offsetHeight / 2 - e.view.offsetHeight / 2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
49
doric-web/src/shader/DoricTextNode.ts
Normal file
49
doric-web/src/shader/DoricTextNode.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { DoricViewNode, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString, toRGBAString } from "./DoricViewNode";
|
||||
|
||||
export class DoricTextNode extends DoricViewNode {
|
||||
textElement!: HTMLElement
|
||||
build(): HTMLElement {
|
||||
const div = document.createElement('div')
|
||||
div.style.display = "flex"
|
||||
this.textElement = document.createElement('span')
|
||||
div.appendChild(this.textElement)
|
||||
div.style.justifyContent = "center"
|
||||
div.style.alignItems = "center"
|
||||
return div
|
||||
}
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
switch (propName) {
|
||||
case 'text':
|
||||
this.textElement.innerText = prop
|
||||
break
|
||||
case 'textSize':
|
||||
v.style.fontSize = toPixelString(prop)
|
||||
break
|
||||
case 'textColor':
|
||||
v.style.color = toRGBAString(prop)
|
||||
break
|
||||
case 'textAlignment':
|
||||
const gravity: number = prop
|
||||
if ((gravity & LEFT) === LEFT) {
|
||||
v.style.justifyContent = "flex-start"
|
||||
} else if ((gravity & RIGHT) === RIGHT) {
|
||||
v.style.justifyContent = "flex-end"
|
||||
} else if ((gravity & CENTER_X) === CENTER_X) {
|
||||
v.style.justifyContent = "center"
|
||||
}
|
||||
if ((gravity & TOP) === TOP) {
|
||||
v.style.alignItems = "flex-start"
|
||||
} else if ((gravity & BOTTOM) === BOTTOM) {
|
||||
v.style.alignItems = "flex-end"
|
||||
} else if ((gravity & CENTER_Y) === CENTER_Y) {
|
||||
v.style.alignItems = "center"
|
||||
}
|
||||
break
|
||||
default:
|
||||
super.blendProps(v, propName, prop)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
60
doric-web/src/shader/DoricVLayoutNode.ts
Normal file
60
doric-web/src/shader/DoricVLayoutNode.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { DoricGroupViewNode, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString } from "./DoricViewNode";
|
||||
|
||||
export class DoricVLayoutNode extends DoricGroupViewNode {
|
||||
space = 0
|
||||
gravity = 0
|
||||
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.display = "flex"
|
||||
ret.style.flexDirection = "column"
|
||||
ret.style.flexWrap = "nowrap"
|
||||
return ret
|
||||
}
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'space') {
|
||||
this.space = prop
|
||||
} else if (propName === 'gravity') {
|
||||
this.gravity = prop
|
||||
if ((this.gravity & LEFT) === LEFT) {
|
||||
this.view.style.alignItems = "flex-start"
|
||||
} else if ((this.gravity & RIGHT) === RIGHT) {
|
||||
this.view.style.alignItems = "flex-end"
|
||||
} else if ((this.gravity & CENTER_X) === CENTER_X) {
|
||||
this.view.style.alignItems = "center"
|
||||
}
|
||||
if ((this.gravity & TOP) === TOP) {
|
||||
this.view.style.justifyContent = "flex-start"
|
||||
} else if ((this.gravity & BOTTOM) === BOTTOM) {
|
||||
this.view.style.justifyContent = "flex-end"
|
||||
} else if ((this.gravity & CENTER_Y) === CENTER_Y) {
|
||||
this.view.style.justifyContent = "center"
|
||||
}
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
|
||||
layout() {
|
||||
super.layout()
|
||||
this.childNodes.forEach((e, idx) => {
|
||||
e.view.style.flexShrink = "0"
|
||||
if (e.layoutConfig?.weight) {
|
||||
e.view.style.flex = `${e.layoutConfig?.weight}`
|
||||
}
|
||||
e.view.style.marginTop = toPixelString(e.layoutConfig?.margin?.top || 0)
|
||||
e.view.style.marginBottom = toPixelString(
|
||||
(idx === this.childNodes.length - 1) ? 0 : this.space
|
||||
+ (e.layoutConfig?.margin?.bottom || 0))
|
||||
e.view.style.marginLeft = toPixelString(e.layoutConfig?.margin?.left || 0)
|
||||
e.view.style.marginRight = toPixelString(e.layoutConfig?.margin?.right || 0)
|
||||
if ((e.layoutConfig.alignment & LEFT) === LEFT) {
|
||||
e.view.style.alignSelf = "flex-start"
|
||||
} else if ((e.layoutConfig.alignment & RIGHT) === RIGHT) {
|
||||
e.view.style.alignSelf = "flex-end"
|
||||
} else if ((e.layoutConfig.alignment & CENTER_X) === CENTER_X) {
|
||||
e.view.style.alignSelf = "center"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
491
doric-web/src/shader/DoricViewNode.ts
Normal file
491
doric-web/src/shader/DoricViewNode.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
import { DoricContext } from "../DoricContext";
|
||||
import { acquireViewNode } from "../DoricRegistry";
|
||||
|
||||
export enum LayoutSpec {
|
||||
EXACTLY = 0,
|
||||
WRAP_CONTENT = 1,
|
||||
AT_MOST = 2,
|
||||
}
|
||||
|
||||
const SPECIFIED = 1
|
||||
const START = 1 << 1
|
||||
const END = 1 << 2
|
||||
|
||||
const SHIFT_X = 0
|
||||
const SHIFT_Y = 4
|
||||
|
||||
export const LEFT = (START | SPECIFIED) << SHIFT_X
|
||||
export const RIGHT = (END | SPECIFIED) << SHIFT_X
|
||||
|
||||
export const TOP = (START | SPECIFIED) << SHIFT_Y
|
||||
export const BOTTOM = (END | SPECIFIED) << SHIFT_Y
|
||||
|
||||
export const CENTER_X = SPECIFIED << SHIFT_X
|
||||
export const CENTER_Y = SPECIFIED << SHIFT_Y
|
||||
|
||||
export const CENTER = CENTER_X | CENTER_Y
|
||||
|
||||
export type FrameSize = {
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
export function toPixelString(v: number) {
|
||||
return `${v}px`
|
||||
}
|
||||
|
||||
export function toRGBAString(color: number) {
|
||||
let strs = []
|
||||
for (let i = 0; i < 32; i += 8) {
|
||||
|
||||
strs.push(((color >> i) & 0xff).toString(16))
|
||||
}
|
||||
strs = strs.map(e => {
|
||||
if (e.length === 1) {
|
||||
return '0' + e
|
||||
}
|
||||
return e
|
||||
}).reverse()
|
||||
/// RGBA
|
||||
return `#${strs[1]}${strs[2]}${strs[3]}${strs[0]}`
|
||||
}
|
||||
|
||||
|
||||
export type DoricViewNodeClass = { new(...args: any[]): {} }
|
||||
|
||||
export interface DVModel {
|
||||
id: string,
|
||||
type: string,
|
||||
props: {
|
||||
[index: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export abstract class DoricViewNode {
|
||||
viewId = ""
|
||||
viewType = "View"
|
||||
context: DoricContext
|
||||
superNode?: DoricSuperNode
|
||||
layoutConfig = {
|
||||
widthSpec: LayoutSpec.EXACTLY,
|
||||
heightSpec: LayoutSpec.EXACTLY,
|
||||
alignment: 0,
|
||||
weight: 0,
|
||||
margin: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0
|
||||
}
|
||||
}
|
||||
padding = {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
|
||||
border?: {
|
||||
width: number,
|
||||
color: number,
|
||||
}
|
||||
|
||||
frameWidth = 0
|
||||
|
||||
frameHeight = 0
|
||||
|
||||
offsetX = 0
|
||||
|
||||
offsetY = 0
|
||||
|
||||
view!: HTMLElement
|
||||
|
||||
|
||||
constructor(context: DoricContext) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
init(superNode?: DoricSuperNode) {
|
||||
if (superNode) {
|
||||
this.superNode = superNode
|
||||
if (this instanceof DoricSuperNode) {
|
||||
this.reusable = superNode.reusable
|
||||
}
|
||||
}
|
||||
this.view = this.build()
|
||||
}
|
||||
|
||||
abstract build(): HTMLElement
|
||||
|
||||
get paddingLeft() {
|
||||
return this.padding.left || 0
|
||||
}
|
||||
|
||||
get paddingRight() {
|
||||
return this.padding.right || 0
|
||||
}
|
||||
|
||||
get paddingTop() {
|
||||
return this.padding.top || 0
|
||||
}
|
||||
|
||||
get paddingBottom() {
|
||||
return this.padding.bottom || 0
|
||||
}
|
||||
|
||||
get borderWidth() {
|
||||
return this.border?.width || 0
|
||||
}
|
||||
|
||||
blend(props: { [index: string]: any }) {
|
||||
this.view.id = `${this.viewId}`
|
||||
for (let key in props) {
|
||||
this.blendProps(this.view, key, props[key])
|
||||
}
|
||||
this.onBlended()
|
||||
this.layout()
|
||||
}
|
||||
onBlended() {
|
||||
|
||||
}
|
||||
configBorder() {
|
||||
if (this.border) {
|
||||
this.view.style.borderStyle = "solid"
|
||||
this.view.style.borderWidth = toPixelString(this.border.width)
|
||||
this.view.style.borderColor = toRGBAString(this.border.color)
|
||||
}
|
||||
}
|
||||
|
||||
configWidth() {
|
||||
switch (this.layoutConfig.widthSpec) {
|
||||
case LayoutSpec.WRAP_CONTENT:
|
||||
this.view.style.width = "max-content"
|
||||
break
|
||||
|
||||
case LayoutSpec.AT_MOST:
|
||||
this.view.style.width = "100%"
|
||||
break
|
||||
|
||||
case LayoutSpec.EXACTLY:
|
||||
default:
|
||||
this.view.style.width = toPixelString(this.frameWidth
|
||||
- this.paddingLeft - this.paddingRight
|
||||
- this.borderWidth * 2)
|
||||
break
|
||||
}
|
||||
}
|
||||
configHeight() {
|
||||
switch (this.layoutConfig.heightSpec) {
|
||||
case LayoutSpec.WRAP_CONTENT:
|
||||
this.view.style.height = "max-content"
|
||||
break
|
||||
|
||||
case LayoutSpec.AT_MOST:
|
||||
this.view.style.height = "100%"
|
||||
break
|
||||
|
||||
case LayoutSpec.EXACTLY:
|
||||
default:
|
||||
this.view.style.height = toPixelString(this.frameHeight
|
||||
- this.paddingTop - this.paddingBottom
|
||||
- this.borderWidth * 2)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
configMargin() {
|
||||
if (this.layoutConfig.margin) {
|
||||
this.view.style.marginLeft = toPixelString(this.layoutConfig.margin.left || 0)
|
||||
this.view.style.marginRight = toPixelString(this.layoutConfig.margin.right || 0)
|
||||
this.view.style.marginTop = toPixelString(this.layoutConfig.margin.top || 0)
|
||||
this.view.style.marginBottom = toPixelString(this.layoutConfig.margin.bottom || 0)
|
||||
}
|
||||
}
|
||||
|
||||
configPadding() {
|
||||
if (this.padding) {
|
||||
this.view.style.paddingLeft = toPixelString(this.paddingLeft)
|
||||
this.view.style.paddingRight = toPixelString(this.paddingRight)
|
||||
this.view.style.paddingTop = toPixelString(this.paddingTop)
|
||||
this.view.style.paddingBottom = toPixelString(this.paddingBottom)
|
||||
}
|
||||
}
|
||||
|
||||
layout() {
|
||||
this.configMargin()
|
||||
this.configBorder()
|
||||
this.configPadding()
|
||||
this.configWidth()
|
||||
this.configHeight()
|
||||
}
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
switch (propName) {
|
||||
case "border":
|
||||
this.border = prop
|
||||
break
|
||||
case "padding":
|
||||
this.padding = prop
|
||||
break
|
||||
case 'width':
|
||||
this.frameWidth = prop as number
|
||||
break
|
||||
case 'height':
|
||||
this.frameHeight = prop as number
|
||||
break
|
||||
case 'backgroundColor':
|
||||
this.backgroundColor = prop as number
|
||||
break
|
||||
case 'layoutConfig':
|
||||
const layoutConfig = prop
|
||||
for (let key in layoutConfig) {
|
||||
Reflect.set(this.layoutConfig, key, Reflect.get(layoutConfig, key, layoutConfig))
|
||||
}
|
||||
break
|
||||
case 'x':
|
||||
this.offsetX = prop as number
|
||||
break
|
||||
case 'y':
|
||||
this.offsetY = prop as number
|
||||
break
|
||||
case 'onClick':
|
||||
this.view.onclick = (event: Event) => {
|
||||
this.callJSResponse(prop as string)
|
||||
event.stopPropagation()
|
||||
}
|
||||
break
|
||||
case 'corners':
|
||||
if (typeof prop === 'object') {
|
||||
this.view.style.borderTopLeftRadius = toPixelString(prop.leftTop)
|
||||
this.view.style.borderTopRightRadius = toPixelString(prop.rightTop)
|
||||
this.view.style.borderBottomRightRadius = toPixelString(prop.rightBottom)
|
||||
this.view.style.borderBottomLeftRadius = toPixelString(prop.leftBottom)
|
||||
} else {
|
||||
this.view.style.borderRadius = toPixelString(prop)
|
||||
}
|
||||
break
|
||||
case 'shadow':
|
||||
const opacity = prop.opacity || 0
|
||||
if (opacity > 0) {
|
||||
const offsetX = prop.offsetX || 0
|
||||
const offsetY = prop.offsetY || 0
|
||||
const shadowColor = prop.color || 0xff000000
|
||||
const shadowRadius = prop.radius
|
||||
const alpha = opacity * 255
|
||||
this.view.style.boxShadow = `${toPixelString(offsetX)} ${toPixelString(offsetY)} ${toPixelString(shadowRadius)} ${toRGBAString((shadowColor & 0xffffff) | ((alpha & 0xff) << 24))} `
|
||||
} else {
|
||||
this.view.style.boxShadow = ""
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
set backgroundColor(v: number) {
|
||||
this.view.style.backgroundColor = toRGBAString(v)
|
||||
}
|
||||
|
||||
static create(context: DoricContext, type: string) {
|
||||
const viewNodeClass = acquireViewNode(type)
|
||||
if (viewNodeClass === undefined) {
|
||||
console.error(`Cannot find ViewNode for ${type}`)
|
||||
return undefined
|
||||
}
|
||||
const ret = new viewNodeClass(context) as DoricViewNode
|
||||
ret.viewType = type
|
||||
return ret
|
||||
}
|
||||
|
||||
getIdList() {
|
||||
const ids: string[] = []
|
||||
let viewNode: DoricViewNode | undefined = this
|
||||
do {
|
||||
ids.push(viewNode.viewId)
|
||||
viewNode = viewNode.superNode
|
||||
} while (viewNode)
|
||||
return ids.reverse()
|
||||
}
|
||||
|
||||
callJSResponse(funcId: string, ...args: any) {
|
||||
const argumentsList: any = ['__response__', this.getIdList(), funcId]
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
argumentsList.push(arguments[i])
|
||||
}
|
||||
return Reflect.apply(this.context.invokeEntityMethod, this.context, argumentsList)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export abstract class DoricSuperNode extends DoricViewNode {
|
||||
reusable = false
|
||||
|
||||
subModels: Map<String, DVModel> = new Map
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'subviews') {
|
||||
if (prop instanceof Array) {
|
||||
prop.forEach((e: DVModel) => {
|
||||
this.mixinSubModel(e)
|
||||
this.blendSubNode(e)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
|
||||
mixinSubModel(subNode: DVModel) {
|
||||
const oldValue = this.getSubModel(subNode.id)
|
||||
if (oldValue) {
|
||||
this.mixin(subNode, oldValue)
|
||||
} else {
|
||||
this.subModels.set(subNode.id, subNode)
|
||||
}
|
||||
}
|
||||
|
||||
getSubModel(id: string) {
|
||||
return this.subModels.get(id)
|
||||
}
|
||||
|
||||
mixin(src: DVModel, target: DVModel) {
|
||||
for (let key in src.props) {
|
||||
if (key === "subviews") {
|
||||
continue
|
||||
}
|
||||
Reflect.set(target.props, key, Reflect.get(src.props, key))
|
||||
}
|
||||
}
|
||||
clearSubModels() {
|
||||
this.subModels.clear()
|
||||
}
|
||||
|
||||
removeSubModel(id: string) {
|
||||
this.subModels.delete(id)
|
||||
}
|
||||
|
||||
abstract blendSubNode(model: DVModel): void
|
||||
|
||||
abstract getSubNodeById(viewId: string): DoricViewNode | undefined
|
||||
}
|
||||
|
||||
export abstract class DoricGroupViewNode extends DoricSuperNode {
|
||||
childNodes: DoricViewNode[] = []
|
||||
childViewIds: string[] = []
|
||||
|
||||
init(superNode?: DoricSuperNode) {
|
||||
super.init(superNode)
|
||||
this.view.style.overflow = "hidden"
|
||||
}
|
||||
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'children') {
|
||||
if (prop instanceof Array) {
|
||||
this.childViewIds = prop
|
||||
}
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
|
||||
blend(props: { [index: string]: any }) {
|
||||
super.blend(props)
|
||||
}
|
||||
onBlended() {
|
||||
super.onBlended()
|
||||
this.configChildNode()
|
||||
}
|
||||
configChildNode() {
|
||||
this.childViewIds.forEach((childViewId, index) => {
|
||||
const model = this.getSubModel(childViewId)
|
||||
if (model === undefined) {
|
||||
return
|
||||
}
|
||||
if (index < this.childNodes.length) {
|
||||
const oldNode = this.childNodes[index]
|
||||
if (oldNode.viewId === childViewId) {
|
||||
//The same,skip
|
||||
} else {
|
||||
if (this.reusable) {
|
||||
if (oldNode.viewType === model.type) {
|
||||
//Same type,can be reused
|
||||
oldNode.viewId = childViewId
|
||||
oldNode.blend(model.props)
|
||||
} else {
|
||||
//Replace this view
|
||||
this.view.removeChild(oldNode.view)
|
||||
const newNode = DoricViewNode.create(this.context, model.type)
|
||||
if (newNode === undefined) {
|
||||
return
|
||||
}
|
||||
newNode.viewId = childViewId
|
||||
newNode.init(this)
|
||||
newNode.blend(model.props)
|
||||
this.childNodes[index] = newNode
|
||||
this.view.replaceChild(newNode.view, oldNode.view)
|
||||
}
|
||||
} else {
|
||||
//Find in remain nodes
|
||||
let position = -1
|
||||
for (let start = index + 1; start < this.childNodes.length; start++) {
|
||||
if (childViewId === this.childNodes[start].viewId) {
|
||||
//Found
|
||||
position = start
|
||||
break
|
||||
}
|
||||
}
|
||||
if (position >= 0) {
|
||||
//Found swap idx,position
|
||||
const reused = this.childNodes[position]
|
||||
const abandoned = this.childNodes[index]
|
||||
this.childNodes[index] = reused
|
||||
this.childNodes[position] = abandoned
|
||||
this.view.removeChild(reused.view)
|
||||
this.view.insertBefore(reused.view, abandoned.view)
|
||||
this.view.removeChild(abandoned.view)
|
||||
if (position === this.view.childElementCount - 1) {
|
||||
this.view.appendChild(abandoned.view)
|
||||
} else {
|
||||
this.view.insertBefore(abandoned.view, this.view.children[position])
|
||||
}
|
||||
} else {
|
||||
//Not found,insert
|
||||
const newNode = DoricViewNode.create(this.context, model.type)
|
||||
if (newNode === undefined) {
|
||||
return
|
||||
}
|
||||
newNode.viewId = childViewId
|
||||
newNode.init(this)
|
||||
newNode.blend(model.props)
|
||||
this.childNodes[index] = newNode
|
||||
this.view.insertBefore(newNode.view, this.view.children[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Insert
|
||||
const newNode = DoricViewNode.create(this.context, model.type)
|
||||
if (newNode === undefined) {
|
||||
return
|
||||
}
|
||||
newNode.viewId = childViewId
|
||||
newNode.init(this)
|
||||
newNode.blend(model.props)
|
||||
this.childNodes.push(newNode)
|
||||
this.view.appendChild(newNode.view)
|
||||
}
|
||||
})
|
||||
let size = this.childNodes.length
|
||||
for (let idx = this.childViewIds.length; idx < size; idx++) {
|
||||
this.view.removeChild(this.childNodes[idx].view)
|
||||
}
|
||||
this.childNodes = this.childNodes.slice(0, this.childViewIds.length)
|
||||
}
|
||||
|
||||
blendSubNode(model: DVModel) {
|
||||
this.getSubNodeById(model.id)?.blend(model.props)
|
||||
}
|
||||
|
||||
getSubNodeById(viewId: string) {
|
||||
return this.childNodes.filter(e => e.viewId === viewId)[0]
|
||||
}
|
||||
|
||||
}
|
64
doric-web/tsconfig.json
Normal file
64
doric-web/tsconfig.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"lib": [
|
||||
"DOM"
|
||||
], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "build/", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user