├── entry
├── .gitignore
├── package.json
├── src
│ ├── main
│ │ ├── js
│ │ │ └── default
│ │ │ │ ├── pages
│ │ │ │ └── index
│ │ │ │ │ ├── index.css
│ │ │ │ │ ├── index.hml
│ │ │ │ │ └── index.js
│ │ │ │ ├── i18n
│ │ │ │ ├── zh-CN.json
│ │ │ │ └── en-US.json
│ │ │ │ ├── common
│ │ │ │ └── images
│ │ │ │ │ ├── bg-tv.jpg
│ │ │ │ │ └── Wallpaper.png
│ │ │ │ ├── app.js
│ │ │ │ └── components
│ │ │ │ ├── index.hml
│ │ │ │ ├── index.css
│ │ │ │ ├── table
│ │ │ │ ├── alphabet.js
│ │ │ │ ├── canvas2d.js
│ │ │ │ ├── cell-render.js
│ │ │ │ ├── range.js
│ │ │ │ ├── index.js
│ │ │ │ ├── render.js
│ │ │ │ ├── viewport.js
│ │ │ │ └── area.js
│ │ │ │ └── index.js
│ │ ├── resources
│ │ │ └── base
│ │ │ │ ├── media
│ │ │ │ └── icon.png
│ │ │ │ └── element
│ │ │ │ └── string.json
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── sheet
│ │ │ │ ├── MyApplication.java
│ │ │ │ └── MainAbility.java
│ │ └── config.json
│ └── ohosTest
│ │ ├── js
│ │ ├── test
│ │ │ ├── List.test.js
│ │ │ └── ExampleJsunit.test.js
│ │ └── default
│ │ │ ├── i18n
│ │ │ ├── zh-CN.json
│ │ │ └── en-US.json
│ │ │ ├── pages
│ │ │ └── index
│ │ │ │ ├── index.hml
│ │ │ │ ├── index.css
│ │ │ │ └── index.js
│ │ │ └── app.js
│ │ └── resources
│ │ └── base
│ │ ├── media
│ │ └── icon.png
│ │ └── element
│ │ └── string.json
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── .idea
├── .gitignore
├── compiler.xml
├── vcs.xml
├── previewer
│ ├── previewConfig.json
│ └── phone
│ │ └── phoneSettingConfig_-172715701.json
├── misc.xml
├── gradle.xml
└── jarRepositories.xml
├── screenshots
├── 1.gif
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.gif
├── 8.png
├── 9.gif
└── 10.png
├── .gitattributes
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/entry/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/entry/package.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':entry'
2 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/pages/index/index.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/entry/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # config module specific ProGuard rules here.
--------------------------------------------------------------------------------
/entry/src/ohosTest/js/test/List.test.js:
--------------------------------------------------------------------------------
1 | require('./ExampleJsunit.test.js')
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/screenshots/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/1.gif
--------------------------------------------------------------------------------
/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/2.png
--------------------------------------------------------------------------------
/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/3.png
--------------------------------------------------------------------------------
/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/4.png
--------------------------------------------------------------------------------
/screenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/5.png
--------------------------------------------------------------------------------
/screenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/6.png
--------------------------------------------------------------------------------
/screenshots/7.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/7.gif
--------------------------------------------------------------------------------
/screenshots/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/8.png
--------------------------------------------------------------------------------
/screenshots/9.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/9.gif
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/screenshots/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/screenshots/10.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/entry/src/main/js/default/i18n/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "strings": {
3 | "hello": "您好",
4 | "world": "世界"
5 | }
6 | }
--------------------------------------------------------------------------------
/entry/src/main/js/default/i18n/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "strings": {
3 | "hello": "Hello",
4 | "world": "World"
5 | }
6 | }
--------------------------------------------------------------------------------
/entry/src/main/resources/base/media/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/entry/src/main/resources/base/media/icon.png
--------------------------------------------------------------------------------
/entry/src/main/js/default/common/images/bg-tv.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/entry/src/main/js/default/common/images/bg-tv.jpg
--------------------------------------------------------------------------------
/entry/src/ohosTest/resources/base/media/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/entry/src/ohosTest/resources/base/media/icon.png
--------------------------------------------------------------------------------
/entry/src/ohosTest/js/default/i18n/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "strings": {
3 | "hello": "您好",
4 | "world": "世界"
5 | },
6 | "Files": {
7 | }
8 | }
--------------------------------------------------------------------------------
/entry/src/main/js/default/common/images/Wallpaper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/sheet/main/entry/src/main/js/default/common/images/Wallpaper.png
--------------------------------------------------------------------------------
/entry/src/ohosTest/js/default/i18n/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "strings": {
3 | "hello": "Hello",
4 | "world": "World"
5 | },
6 | "Files": {
7 | }
8 | }
--------------------------------------------------------------------------------
/entry/src/ohosTest/js/default/pages/index/index.hml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t('strings.hello') }} {{title}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/previewer/previewConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "1.0.0": {
3 | "LastPreviewDevice": {
4 | "/Users/eno/DevEcoStudioProjects/sheet/entry/.preview/intermediates/res/debug": [
5 | "phone"
6 | ]
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/entry/src/ohosTest/js/default/pages/index/index.css:
--------------------------------------------------------------------------------
1 | .container {
2 | flex-direction: column;
3 | justify-content: center;
4 | align-items: center;
5 | }
6 |
7 | .title {
8 | font-size: 100px;
9 | }
10 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/app.js:
--------------------------------------------------------------------------------
1 | export default {
2 | onCreate() {
3 | console.info('AceApplication onCreate');
4 | },
5 | onDestroy() {
6 | console.info('AceApplication onDestroy');
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/entry/src/ohosTest/js/default/app.js:
--------------------------------------------------------------------------------
1 | export default {
2 | onCreate() {
3 | console.info('TestApplication onCreate');
4 | },
5 | onDestroy() {
6 | console.info('TestApplication onDestroy');
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://repo.huaweicloud.com/gradle/gradle-6.3-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/entry/src/main/resources/base/element/string.json:
--------------------------------------------------------------------------------
1 | {
2 | "string":[
3 | {
4 | "name":"entry_MainAbility",
5 | "value":"entry_MainAbility"
6 | },
7 | {
8 | "name":"mainability_description",
9 | "value":"JS_Empty Ability"
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | /entry/.preview
15 | .cxx
16 |
--------------------------------------------------------------------------------
/entry/src/main/java/com/example/sheet/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.example.sheet;
2 |
3 | import ohos.aafwk.ability.AbilityPackage;
4 |
5 | public class MyApplication extends AbilityPackage {
6 | @Override
7 | public void onInitialize(){
8 | super.onInitialize();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/pages/index/index.hml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/entry/src/ohosTest/resources/base/element/string.json:
--------------------------------------------------------------------------------
1 | {
2 | "string":[
3 | {
4 | "name":"app_name",
5 | "value":"sheet"
6 | },
7 | {
8 | "name": "mainability_description",
9 | "value": "hap sample empty page"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/entry/src/ohosTest/js/test/ExampleJsunit.test.js:
--------------------------------------------------------------------------------
1 | import app from '@system.app'
2 | import {describe, beforeAll, beforeEach, afterEach, afterAll, it, expect} from 'deccjsunit/index'
3 | describe('appInfoTest', function () {
4 | it('app_info_test_001', 0, function () {
5 | var info = app.getInfo()
6 | expect(info.versionName).assertEqual('1.0')
7 | expect(info.versionCode).assertEqual('3')
8 | })
9 | })
--------------------------------------------------------------------------------
/entry/src/main/java/com/example/sheet/MainAbility.java:
--------------------------------------------------------------------------------
1 | package com.example.sheet;
2 |
3 | import ohos.ace.ability.AceAbility;
4 | import ohos.aafwk.content.Intent;
5 |
6 | public class MainAbility extends AceAbility {
7 | @Override
8 | public void onStart(Intent intent) {
9 | super.onStart(intent);
10 | }
11 |
12 | @Override
13 | public void onStop() {
14 | super.onStop();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.idea/previewer/phone/phoneSettingConfig_-172715701.json:
--------------------------------------------------------------------------------
1 | {
2 | "setting": {
3 | "1.0.1": {
4 | "Language": {
5 | "args": {
6 | "Language": "zh-CN"
7 | }
8 | }
9 | }
10 | },
11 | "frontend": {
12 | "1.0.0": {
13 | "Resolution": {
14 | "args": {
15 | "Resolution": "360*780"
16 | }
17 | },
18 | "DeviceType": {
19 | "args": {
20 | "DeviceType": "phone"
21 | }
22 | }
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/index.hml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/index.css:
--------------------------------------------------------------------------------
1 | .area {
2 | position: absolute;
3 | text-align: left;
4 | border: 2px solid #4b89ff;
5 | line-height: 0;
6 | z-index: 100;
7 | background-color: #ffffff;
8 | pointer-events: auto;
9 | }
10 |
11 | textarea {
12 | box-sizing: content-box;
13 | padding: 2 3px;
14 | outline: none;
15 | resize: none;
16 | text-align: center;
17 | overflow-y: hidden;
18 | font: 400 13px Arial, 'Lato', 'Source Sans Pro', Roboto, Helvetica, sans-serif;
19 | white-space: normal;
20 | word-wrap: break-word;
21 | line-height: 25px;
22 | margin: 0;
23 | }
24 |
25 | .canvas-container{
26 | position: fixed;
27 | top: 0px;
28 | left: 0px;
29 | width: 100%;
30 | height: 100%;
31 | background-color: cornflowerblue;
32 | }
33 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. DevEco Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | # If the Chinese output is garbled, please configure the following parameter.
10 | # This function is enabled by default when the DevEco Studio builds the hap/app,if you need disable gradle parallel,you should set org.gradle.parallel false.
11 | # more information see https://docs.gradle.org/current/userguide/performance.html
12 | # org.gradle.parallel=false
13 | # org.gradle.jvmargs=-Dfile.encoding=GBK
--------------------------------------------------------------------------------
/entry/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.huawei.ohos.hap'
2 | apply plugin: 'com.huawei.ohos.decctest'
3 | //For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#ZH-CN_TOPIC_0000001154985555__section1112183053510
4 | ohos {
5 | compileSdkVersion 5
6 | defaultConfig {
7 | compatibleSdkVersion 5
8 | }
9 | buildTypes {
10 | release {
11 | proguardOpt {
12 | proguardEnabled false
13 | rulesFiles 'proguard-rules.pro'
14 | }
15 | }
16 | }
17 |
18 | }
19 |
20 | dependencies {
21 | implementation fileTree(dir: 'libs', include: ['*.jar', '*.har'])
22 | testImplementation 'junit:junit:4.13'
23 | ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.100'
24 | }
25 | decc {
26 | supportType = ['html','xml']
27 | }
28 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
22 |
23 |
--------------------------------------------------------------------------------
/entry/src/main/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "app":{
3 | "bundleName":"com.example.sheet",
4 | "vendor":"example",
5 | "version":{
6 | "code":1000000,
7 | "name":"1.0.0"
8 | }
9 | },
10 | "deviceConfig":{},
11 | "module":{
12 | "package":"com.example.sheet",
13 | "name":".MyApplication",
14 | "mainAbility":"com.example.sheet.MainAbility",
15 | "deviceType":[
16 | "phone"
17 | ],
18 | "distro":{
19 | "deliveryWithInstall":true,
20 | "moduleName":"entry",
21 | "moduleType":"entry",
22 | "installationFree":true
23 | },
24 | "abilities":[
25 | {
26 | "skills":[
27 | {
28 | "entities":[
29 | "entity.system.home"
30 | ],
31 | "actions":[
32 | "action.system.home"
33 | ]
34 | }
35 | ],
36 | "name":"com.example.sheet.MainAbility",
37 | "icon":"$media:icon",
38 | "description":"$string:mainability_description",
39 | "label":"$string:entry_MainAbility",
40 | "type":"page",
41 | "launchType":"standard"
42 | }
43 | ],
44 | "js":[
45 | {
46 | "pages":[
47 | "pages/index/index"
48 | ],
49 | "name":"default",
50 | "window":{
51 | "designWidth":720,
52 | "autoDesignWidth":true
53 | }
54 | }
55 | ]
56 | }
57 | }
--------------------------------------------------------------------------------
/entry/src/ohosTest/js/default/pages/index/index.js:
--------------------------------------------------------------------------------
1 | import file from '@system.file'
2 | import app from '@system.app'
3 | import device from '@system.device'
4 | import router from '@system.router'
5 | import {Core, ExpectExtend, ReportExtend, InstrumentLog} from 'deccjsunit/index'
6 |
7 | export default {
8 | data: {
9 | title: ""
10 | },
11 | onInit() {
12 | this.title = this.$t('strings.world');
13 | },
14 | onShow() {
15 | console.info('onShow finish')
16 | const core = Core.getInstance()
17 | const expectExtend = new ExpectExtend({
18 | 'id': 'extend'
19 | })
20 | const reportExtend = new ReportExtend(file)
21 | const instrumentLog = new InstrumentLog({
22 | 'id': 'report'
23 | })
24 | core.addService('expect', expectExtend)
25 | core.addService('report', reportExtend)
26 | core.addService('report', instrumentLog)
27 | core.init()
28 | core.subscribeEvent('spec', instrumentLog)
29 | core.subscribeEvent('suite', instrumentLog)
30 | core.subscribeEvent('task', instrumentLog)
31 |
32 | const configService = core.getDefaultService('config')
33 | configService.setConfig(this)
34 |
35 | require('../../../test/List.test')
36 | core.execute()
37 | },
38 | onReady() {
39 | },
40 | }
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/table/alphabet.js:
--------------------------------------------------------------------------------
1 | const alphabets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
2 |
3 | export function stringAt(index) {
4 | let str = '';
5 | let cindex = index;
6 | while (cindex >= alphabets.length) {
7 | cindex /= alphabets.length;
8 | cindex -= 1;
9 | str += alphabets[parseInt(cindex, 10) % alphabets.length];
10 | }
11 | const last = index % alphabets.length;
12 | str += alphabets[last];
13 | return str;
14 | }
15 |
16 | export function indexAt(str) {
17 | let ret = 0;
18 | for (let i = 0; i < str.length - 1; i += 1) {
19 | const cindex = str.charCodeAt(i) - 65;
20 | const exponet = str.length - 1 - i;
21 | ret += (alphabets.length ** exponet) + (alphabets.length * cindex);
22 | }
23 | ret += str.charCodeAt(str.length - 1) - 65;
24 | return ret;
25 | }
26 |
27 | // B10 => x,y
28 | export function expr2xy(src) {
29 | let x = '';
30 | let y = '';
31 | for (let i = 0; i < src.length; i += 1) {
32 | if (src.charAt(i) >= '0' && src.charAt(i) <= '9') {
33 | y += src.charAt(i);
34 | } else {
35 | x += src.charAt(i).toUpperCase();
36 | }
37 | }
38 | return [indexAt(x), parseInt(y, 10) - 1];
39 | }
40 |
41 | // x,y => B10
42 | export function xy2expr(x, y) {
43 | return `${stringAt(x)}${y + 1}`;
44 | }
45 |
46 | export function expr2expr(src, xn, yn) {
47 | const [x, y] = expr2xy(src);
48 | return xy2expr(x + xn, y + yn);
49 | }
50 |
51 | export default {
52 | stringAt,
53 | indexAt,
54 | expr2xy,
55 | xy2expr,
56 | expr2expr,
57 | };
58 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 | @rem Execute Gradle
88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
89 |
90 | :end
91 | @rem End local scope for the variables with windows NT shell
92 | if "%ERRORLEVEL%"=="0" goto mainEnd
93 |
94 | :fail
95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
96 | rem the _cmd.exe /c_ return code!
97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
98 | exit /b 1
99 |
100 | :mainEnd
101 | if "%OS%"=="Windows_NT" endlocal
102 |
103 | :omega
104 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/index.js:
--------------------------------------------------------------------------------
1 | import prompt from '@system.prompt';
2 | import Table from './table/';
3 | import Viewport from './table/viewport.js';
4 |
5 | export default {
6 | props: {
7 | sheet: {
8 | type: Array,
9 | default: []
10 | },
11 | canvasWidth: {
12 | type: String,
13 | default: '100%',
14 | },
15 | cavasHeight: {
16 | type: String,
17 | default: '100%',
18 | },
19 | tableWidth: {
20 | type: Number,
21 | default: 850,
22 | },
23 | tableHeight: {
24 | type: Number,
25 | default: 800,
26 | },
27 | },
28 | data: {
29 | isShowArea: false,
30 | areaTop: 0,
31 | areaLeft: 0,
32 | content: '',
33 | isFocus: true,
34 | },
35 | touchstart(evt) {
36 | this.$emit('clickCellStart', {
37 | evt,
38 | el: this.el,
39 | textarea: this.textarea,
40 | viewport: this.viewport,
41 | table: this.table,
42 | });
43 | const cell = this.viewport.cell(evt.touches[0].localX, evt.touches[0].localY);
44 | this.table.$onClick(...cell, evt);
45 | this.setTextarea(...cell, evt);
46 | },
47 | setTableCell() {
48 | this.table.cell((ri, ci) => {
49 | return this.sheet?.[ri]?.[ci] || '';
50 | }).render();
51 | },
52 | touchend(evt) {
53 | this.$emit('clickCellEnd', {
54 | evt,
55 | el: this.el,
56 | textarea: this.textarea,
57 | viewport: this.viewport,
58 | table: this.table,
59 | });
60 | const range = this.viewport.range(evt.changedTouches[0].localX, evt.changedTouches[0].localY);
61 | this.table.selection(range);
62 | this.viewport.render(this.table.$draw);
63 | },
64 | longpress(evt) {
65 | this.$emit('clickCellLongpress');
66 | },
67 | change(evt) {
68 | this.$emit('change', {
69 | evt,
70 | el: this.el,
71 | textarea: this.textarea,
72 | viewport: this.viewport,
73 | table: this.table,
74 | });
75 | this.content = evt.value;
76 | this.sheet[this.row][this.col] = this.content;
77 | this.setTableCell();
78 | },
79 | setTextarea(type, cellRect) {
80 | const textarea = this.textarea;
81 | const area = this.area;
82 | this.isShowArea = false;
83 | switch (type) {
84 | case 4:
85 | const { col, height, row, width, x, y } = cellRect;
86 | let value = this.sheet?.[row]?.[col] || '';
87 | value = typeof value === 'string' || typeof value === 'number' ?
88 | value : value.text
89 | prompt.showToast({
90 | message: `当前值:${value} 位置:第${row + 1}行 第${col + 1}列`,
91 | duration: 3000,
92 | });
93 | this.areaLeft = x - 2;
94 | this.areaTop = y - 1;
95 | this.isShowArea = true;
96 | this.row = row;
97 | this.col = col;
98 | this.content = value;
99 | break;
100 | }
101 | },
102 | onShow() {
103 | this.el = this.$refs.canvas;
104 | this.textarea = this.$refs.textarea;
105 | this.area = this.$refs.area;
106 |
107 | this.table = Table.create(this.el, this.tableWidth, this.tableHeight);
108 | this.setTableCell();
109 | this.viewport = new Viewport(this.table);
110 | this.$emit('sheetShow', {
111 | el: this.el,
112 | textarea: this.textarea,
113 | viewport: this.viewport,
114 | table: this.table,
115 | })
116 | },
117 | onHide() {
118 | this.$emit('sheetHide', {
119 | el: this.el,
120 | textarea: this.textarea,
121 | viewport: this.viewport,
122 | table: this.table,
123 | })
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/table/canvas2d.js:
--------------------------------------------------------------------------------
1 | function dpr() {
2 | return 1;
3 | }
4 |
5 | function npx(px) {
6 | return px * dpr();
7 | }
8 |
9 | function attrRadio(ctx, key, v, cb) {
10 | if (dpr() === 1) cb();
11 | else {
12 | const old = ctx[key];
13 | ctx[key] = v(ctx[key]);
14 | cb();
15 | ctx[key] = old;
16 | }
17 | }
18 |
19 | export default class Canvas2d {
20 | constructor(el) {
21 | this.el = el;
22 | this.ctx = el.getContext('2d');
23 | }
24 |
25 | resize(width, height) {
26 | const { el } = this;
27 | this.clearRect(0, 0, width, height);
28 | el.style.width = `${width}px`;
29 | el.style.height = `${height}px`;
30 | // for Retina display
31 | el.width = npx(width);
32 | el.height = npx(height);
33 | return this;
34 | }
35 |
36 | attr(options) {
37 | if (typeof options === 'string') {
38 | return this.ctx[options];
39 | }
40 | Object.entries(options).forEach(([k, v]) => {
41 | if (v !== undefined && v !== null) {
42 | this.ctx[k] = v;
43 | }
44 | });
45 | return this;
46 | }
47 |
48 | textWidth(txt) {
49 | return this.ctx.measureText(txt).width;
50 | }
51 |
52 | line(...xys) {
53 | if (xys.length > 1) {
54 | this.moveTo(...xys[0]);
55 | for (let i = 1; i < xys.length; i += 1) {
56 | this.lineTo(...xys[i]);
57 | }
58 | this.stroke();
59 | }
60 | return this;
61 | }
62 |
63 | lines(...xyss) {
64 | if (xyss.length > 0) {
65 | xyss.forEach((xys) => this.line(...xys));
66 | }
67 | return this;
68 | }
69 |
70 | // style: thin | medium | thick | dashed | dotted
71 | // color: the line color
72 | lineStyle(style, color) {
73 | const { ctx } = this;
74 | ctx.lineWidth = 1;
75 | ctx.strokeStyle = color;
76 | if (style === 'medium') {
77 | ctx.lineWidth = 2;
78 | } else if (style === 'thick') {
79 | ctx.lineWidth = 3;
80 | } else if (style === 'dashed') {
81 | ctx.setLineDash([3, 2]);
82 | } else if (style === 'dotted') {
83 | ctx.setLineDash([1, 1]);
84 | }
85 | return this;
86 | }
87 |
88 | arc(x, y, radius, ...args) {
89 | this.ctx.arc(npx(x), npx(y), npx(radius), ...args);
90 | return this;
91 | }
92 |
93 | roundRect(x, y, w, h, r) {
94 | this.beginPath();
95 | this.moveTo(x + r, y);
96 | this.arcTo(x + w, y, x + w, y + h, r);
97 | this.arcTo(x + w, y + h, x, y + h, r);
98 | this.arcTo(x, y + h, x, y, r);
99 | this.arcTo(x, y, x + w, y, r);
100 | this.closePath();
101 | return this;
102 | }
103 |
104 | stroke() {
105 | const { ctx } = this;
106 | attrRadio(ctx,
107 | 'lineWidth',
108 | (it) => npx(it),
109 | () => (ctx.stroke()));
110 | return this;
111 | }
112 |
113 | strokeRect(x, y, width, height) {
114 | const { ctx } = this;
115 | attrRadio(ctx,
116 | 'lineWidth',
117 | (it) => npx(it),
118 | () => (ctx.strokeRect(npx(x), npx(y), width, height)));
119 | return this;
120 | }
121 |
122 | static create(el) {
123 | return new Canvas2d(el);
124 | }
125 | }
126 |
127 | [
128 | 'save', 'restore',
129 | 'beginPath', 'closePath',
130 | 'clip', 'fill', 'rotate',
131 | ].forEach((it) => {
132 | Canvas2d.prototype[it] = function (...args) {
133 | this.ctx[it](...args);
134 | return this;
135 | };
136 | });
137 |
138 | ['fillText', 'strokeText'].forEach((it) => {
139 | Canvas2d.prototype[it] = function (...args) {
140 | args[1] = npx(args[1]);
141 | args[2] = npx(args[2]);
142 | const { ctx } = this;
143 | attrRadio(ctx, 'font',
144 | (font) => font.replace(/([\d|\\.]*)(pt|px)/g, (w, size, u) => `${npx(size)}${u}`),
145 | () => (ctx[it](...args)));
146 | return this;
147 | };
148 | });
149 |
150 | // arcTo: 4
151 | Object.entries({
152 | translate: 2,
153 | rect: 2,
154 | fillRect: 2,
155 | clearRect: 2,
156 | moveTo: 2,
157 | lineTo: 2,
158 | arc: 2,
159 | arcTo: 4,
160 | bezierCurveTo: 6,
161 | quadraticCurveTo: 4,
162 | createRadialGradient: 6,
163 | createLinearGradient: 4,
164 | }).forEach(([it, cnt]) => {
165 | Canvas2d.prototype[it] = function (...args) {
166 | const { ctx } = this;
167 | const even = npx(ctx.lineWidth) % 2 === 0;
168 | ctx[it](...args.map((arg, index) => {
169 | let n = npx(arg);
170 | if (!even && index < cnt) n -= 0.5;
171 | return n;
172 | }));
173 | return this;
174 | };
175 | });
176 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/table/cell-render.js:
--------------------------------------------------------------------------------
1 | // align: left | center | right
2 | // width: the width of cell
3 | // padding: the padding of cell
4 | function textx(align, width, padding) {
5 | switch (align) {
6 | case 'left':
7 | return padding;
8 | case 'center':
9 | return width / 2;
10 | case 'right':
11 | return width - padding;
12 | default:
13 | return 0;
14 | }
15 | }
16 |
17 | // align: top | middle | bottom
18 | // height: the height of cell
19 | // txtHeight: the height of text
20 | // padding: the padding of cell
21 | function texty(align, height, txtHeight, padding) {
22 | switch (align) {
23 | case 'top':
24 | return padding;
25 | case 'middle':
26 | return height / 2 - txtHeight / 2;
27 | case 'bottom':
28 | return height - padding - txtHeight;
29 | default:
30 | return 0;
31 | }
32 | }
33 |
34 | // type: underline | strike
35 | // align: left | center | right
36 | // valign: top | middle | bottom
37 | function textLine(type, align, valign, x, y, w, h) {
38 | // y
39 | let ty = 0;
40 | if (type === 'underline') {
41 | if (valign === 'top') {
42 | ty = -h;
43 | } else if (valign === 'middle') {
44 | ty = -h / 2;
45 | }
46 | } else if (type === 'strike') {
47 | if (valign === 'top') {
48 | ty = -h / 2;
49 | } else if (valign === 'bottom') {
50 | ty = h / 2;
51 | }
52 | }
53 | // x
54 | let tx = 0;
55 | if (align === 'center') {
56 | tx = w / 2;
57 | } else if (align === 'right') {
58 | tx = w;
59 | }
60 | return [
61 | [x - tx, y - ty],
62 | [x - tx + w, y - ty],
63 | ];
64 | }
65 |
66 | function renderBorder(draw, width, height, border) {
67 | if (border) {
68 | const {
69 | top, right, bottom, left,
70 | } = border;
71 | draw.save();
72 | if (top) draw.lineStyle(...top).line([0, 0], [width, 0]);
73 | if (right) draw.lineStyle(...right).line([width, 0], [width, height]);
74 | if (bottom) draw.lineStyle(...bottom).line([0, height], [width, height]);
75 | if (left) draw.lineStyle(...left).line([0, 0], [0, height]);
76 | draw.restore();
77 | }
78 | }
79 |
80 | function fontString(family, size, italic, bold) {
81 | if (family && size) {
82 | let font = '';
83 | if (italic) font += 'italic ';
84 | if (bold) font += 'bold ';
85 | return `${font} ${size}pt ${family}`;
86 | }
87 | return undefined;
88 | }
89 |
90 | // draw: Canvas2d
91 | // style:
92 | export function cellRender(draw, text, rect, {
93 | border, fontSize, fontName,
94 | bold, italic, color, bgcolor,
95 | align, valign, underline, strike,
96 | rotate, textwrap, padding,
97 | } = {}) {
98 | // at first move to (left, top)
99 | draw.save().beginPath()
100 | .translate(rect.x, rect.y);
101 |
102 | // border
103 | renderBorder(draw, rect.width, rect.height, border);
104 |
105 | // clip
106 | draw.attr({ fillStyle: bgcolor })
107 | .rect(0.5, 0.5, rect.width - 1, rect.height - 1)
108 | .clip()
109 | .fill();
110 |
111 | // text style
112 | draw.save().beginPath().attr({
113 | textAlign: align,
114 | textBaseline: valign,
115 | font: fontString(fontName, fontSize, italic, bold),
116 | fillStyle: color,
117 | });
118 |
119 | // rotate
120 | if (rotate && rotate > 0) {
121 | draw.rotate(rotate * (Math.PI / 180));
122 | }
123 |
124 | const [xp, yp] = padding || [5, 5];
125 | const tx = textx(align, rect.width, xp);
126 | const txts = text.split('\n');
127 | const innerWidth = rect.width - (xp * 2);
128 | const ntxts = [];
129 | txts.forEach((it) => {
130 | const txtWidth = draw.textWidth(it);
131 | if (textwrap && txtWidth > innerWidth) {
132 | let txtLine = { w: 0, len: 0, start: 0 };
133 | for (let i = 0; i < it.length; i += 1) {
134 | if (txtLine.w > innerWidth) {
135 | ntxts.push(it.substr(txtLine.start, txtLine.len));
136 | txtLine = { w: 0, len: 0, start: i };
137 | }
138 | txtLine.len += 1;
139 | txtLine.w += draw.textWidth(it[i]) + 1;
140 | }
141 | if (txtLine.len > 0) {
142 | ntxts.push(it.substr(txtLine.start, txtLine.len));
143 | }
144 | } else {
145 | ntxts.push(it);
146 | }
147 | });
148 |
149 | const lineHeight = fontSize * 1.425;
150 | const txtHeight = (ntxts.length - 1) * lineHeight;
151 | const lineTypes = [];
152 | if (underline) lineTypes.push('underline');
153 | if (strike) lineTypes.push('strike');
154 | let ty = texty(valign, rect.height, txtHeight, yp);
155 | ntxts.forEach((it) => {
156 | const txtWidth = draw.textWidth(it);
157 | draw.fillText(it, tx, ty);
158 | lineTypes.forEach((type) => {
159 | draw.line(...textLine(type, align, valign, tx, ty, txtWidth, fontSize));
160 | });
161 | ty += lineHeight;
162 | });
163 | draw.restore();
164 |
165 | draw.restore();
166 | }
167 |
168 | export default {};
169 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/table/range.js:
--------------------------------------------------------------------------------
1 | import { expr2xy } from './alphabet';
2 |
3 | /**
4 | * the range specified by a start position and an end position,
5 | * the smallest range must contain at least one cell.
6 | * Range is not a merged cell, but it can be merged as a single cell
7 | */
8 | export default class Range {
9 | constructor(startRow, startCol, endRow, endCol) {
10 | // index of row of the start position
11 | this.startRow = startRow;
12 | // index of col of the start position
13 | this.startCol = startCol;
14 | // index of row of the end position
15 | this.endRow = endRow;
16 | // index of col of the end position
17 | this.endCol = endCol;
18 | }
19 |
20 | get start() {
21 | return [this.startRow, this.startCol];
22 | }
23 |
24 | get end() {
25 | return [this.endRow, this.endCol];
26 | }
27 |
28 | // count of rows contained in this range
29 | get rows() {
30 | return this.endRow - this.startRow;
31 | }
32 |
33 | // count of cols contained in this range
34 | get cols() {
35 | return this.endCol - this.startCol;
36 | }
37 |
38 | // check whether or not this range is empty
39 | get empty() {
40 | return this.rows() === 0 && this.cols() === 0;
41 | }
42 |
43 | /**
44 | * check whether or not the row-index contained in the row of range
45 | * @param {int} index
46 | * @returns {boolean}
47 | */
48 | inRow(index) {
49 | return this.startRow <= index && index <= this.endRow;
50 | }
51 |
52 | /**
53 | * check whether or not the index contained in the col of range
54 | * @param {int} index
55 | * @returns {boolean}
56 | */
57 | inCol(index) {
58 | return this.startCol <= index && index <= this.endCol;
59 | }
60 |
61 | /**
62 | * check whether or not the range contains a cell position(row, col)
63 | * @param {int} row row-index
64 | * @param {int} col col-index
65 | * @returns {boolean}
66 | */
67 | contains(row, col) {
68 | return this.inRow(row) && this.inCol(col);
69 | }
70 |
71 | /**
72 | * check whether or not the range within the other range
73 | * @param {Range} other
74 | * @returns {boolean}
75 | */
76 | within(other) {
77 | return this.startRow >= other.startRow
78 | && this.startCol >= other.startCode
79 | && this.endRow <= other.endRow
80 | && this.endCol <= other.endCol;
81 | }
82 |
83 | /**
84 | * check whether or not the range intersects the other range
85 | * @param {Range} other
86 | * @returns {boolean}
87 | */
88 | intersects(other) {
89 | return this.startRow <= other.endRow
90 | && this.startCol <= other.endCol
91 | && other.startRow <= this.endRow
92 | && other.startCol <= this.endCol;
93 | }
94 |
95 | /**
96 | * the self union the other resulting in the new range
97 | * @param {Range} other
98 | * @returns {Range} the new range
99 | */
100 | union(other) {
101 | return new Range(
102 | other.startRow < this.startRow ? other.startRow : this.startRow,
103 | other.startCol < this.startCol ? other.startCol : this.startCol,
104 | other.endRow > this.endRow ? other.endRow : this.endRow,
105 | other.endCol > this.endCol ? other.endCol : this.endCol,
106 | );
107 | }
108 |
109 | /**
110 | * @param {Function} cb (row) => {}
111 | * @returns this
112 | */
113 | eachRow(cb) {
114 | for (let row = this.startRow; row <= this.endRow; row += 1) {
115 | cb(row);
116 | }
117 | return this;
118 | }
119 |
120 | /**
121 | * @param {Function} cb (col) => {}
122 | * @returns this
123 | */
124 | eachCol(cb) {
125 | for (let col = this.startCol; col <= this.endCol; col += 1) {
126 | cb(col);
127 | }
128 | return this;
129 | }
130 |
131 | /**
132 | * @param {Function} cb (row, col) => {}
133 | * @returns this
134 | */
135 | each(cb) {
136 | this.rowEach((row) => {
137 | this.colEach((col) => (cb(row, col)));
138 | });
139 | return this;
140 | }
141 |
142 | clone() {
143 | return new Range(
144 | this.startRow,
145 | this.startCol,
146 | this.endRow,
147 | this.endCol,
148 | );
149 | }
150 | }
151 |
152 | export function newRange(ref) {
153 | if (ref === undefined) return undefined;
154 | const ary = ref.split(':');
155 | const start = expr2xy(ary[0]);
156 | const end = expr2xy(ary[1]);
157 | return new Range(start[1], start[0], end[1], end[0]);
158 | }
159 |
160 | export function eachRanges(refs, cb) {
161 | if (refs && refs.length > 0) {
162 | refs.forEach((ref) => {
163 | cb(newRange(ref));
164 | });
165 | }
166 | }
167 |
168 | export function findRanges(refs, cb) {
169 | if (refs && refs.length > 0) {
170 | let it = null;
171 | if (refs.find((ref) => {
172 | it = newRange(ref);
173 | return cb(it);
174 | })) {
175 | return it;
176 | }
177 | }
178 | return null;
179 | }
180 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/pages/index/index.js:
--------------------------------------------------------------------------------
1 | import prompt from '@system.prompt';
2 |
3 | const styleText = {
4 | text: 'Hello World!',
5 | style: {
6 | color: 'blue'
7 | }
8 | };
9 | export default {
10 | data: {
11 | sheet: [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
12 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
13 | [styleText, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
14 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
15 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
16 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
17 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
18 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
19 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
20 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
21 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
22 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
23 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
24 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
25 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
26 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
27 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
28 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
29 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
30 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
31 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
32 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
33 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
34 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
35 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
36 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
37 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
38 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
39 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
40 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
41 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
42 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
43 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
44 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
45 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
46 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
47 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
48 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]]
49 | },
50 | clickCellStart(e) {
51 | const evt = e.detail.evt;
52 | },
53 | clickCellEnd(e) {
54 | const evt = e.detail.evt;
55 | },
56 | change(e) {
57 | const evt = e.detail.evt;
58 | },
59 | sheetShow(sheet) {
60 | this.el = sheet.detail.el;
61 | this.textarea = sheet.detail.textarea;
62 | this.viewport = sheet.detail.viewport;
63 | this.table = sheet.detail.table;
64 | },
65 | setTableCell() {
66 | this.table.cell((ri, ci) => {
67 | return this.sheet?.[ri]?.[ci] || '';
68 | }).render();
69 | },
70 |
71 | dialogSuccess(data) {
72 | switch (data.index) {
73 | case 0:
74 | this.table.cell((ri, ci) => {
75 | if (ri === 2 && ci === 2) {
76 | return {
77 | text: '8848',
78 | style: {
79 | color: 'red'
80 | }
81 | }
82 | }
83 | return this.sheet?.[ri]?.[ci] || '';
84 | }).render();
85 | break;
86 | case 1:
87 | this.table.colHeader({ height: 50, rows: 2, merges: ['A1:C1', 'D1:D2'] })
88 | .merges(['G9:H11', 'B9:D11'])
89 | .freeze('C6')
90 | .render();
91 | break;
92 | case 2:
93 | this.table
94 | .scrollRows(2)
95 | .scrollCols(1)
96 | .cell((ri, ci) => {
97 | return '';
98 | }).render();
99 | break;
100 | }
101 | },
102 | clickCellLongpress(evt) {
103 | prompt.showDialog({
104 | buttons: [{
105 | text: '1. 初始化值,修改3行3列为红色8848',
106 | color: '#666666',
107 | }, {
108 | text: '2. 合并单元格,修改行头部和冻结C6',
109 | color: '#666666',
110 | }, {
111 | text: '3. 修改起始行,清空列表数据',
112 | color: '#666666',
113 | }],
114 | success: this.dialogSuccess,
115 | });
116 | },
117 | }
118 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/table/index.js:
--------------------------------------------------------------------------------
1 | import { stringAt, expr2xy } from './alphabet';
2 | import Canvas2d from './canvas2d';
3 | import { newRange, eachRanges } from './range';
4 | import Viewport from './viewport';
5 |
6 | /**
7 | * ----------------------------------------------------------------
8 | * | | column header |
9 | * ----------------------------------------------------------------
10 | * | | |
11 | * | row header | body |
12 | * | | |
13 | * ----------------------------------------------------------------
14 | * row { height, hide, autoFit }
15 | * col { width, hide, autoFit }
16 | * cell {
17 | * text,
18 | * style: {
19 | * border, fontSize, fontName,
20 | * bold, italic, color, bgcolor,
21 | * align, valign, underline, strike,
22 | * rotate, textwrap, padding,
23 | * },
24 | * type: text | button | link | checkbox | radio | list | progress | image | imageButton | date
25 | * }
26 | */
27 | class Table {
28 | // the count of rows
29 | $rows = 100;
30 |
31 | // the count of cols;
32 | $cols = 26;
33 |
34 | /**
35 | * get row given rowIndex
36 | * @param {int} rowIndex
37 | * @returns { height, hide, autoFit } row
38 | */
39 | $row = () => ({ height: 25, hide: false, autoFit: false });
40 |
41 | /**
42 | * get col given colIndex
43 | * @param {int} coIndex
44 | * @returns { width, hide, autoFit } col
45 | */
46 | $col = () => ({ width: 100, hide: false, autoFit: false });
47 |
48 | /**
49 | * get cell given rowIndex, colIndex
50 | * @param {int} rowIndex
51 | * @param {int} colIndex
52 | * @returns { text, style, type, ...} cell
53 | */
54 | $cell = () => '';
55 |
56 | $lineStyle = {
57 | width: 1,
58 | color: '#e6e6e6',
59 | };
60 |
61 | $cellStyle = {
62 | bgcolor: '#ffffff',
63 | align: 'left',
64 | valign: 'middle',
65 | textwrap: true,
66 | underline: false,
67 | color: '#0a0a0a',
68 | bold: false,
69 | italic: false,
70 | rotate: 0,
71 | fontSize: 9,
72 | fontName: 'Source Sans Pro',
73 | };
74 |
75 | $merges = [];
76 |
77 | // row header
78 | $rowHeader = {
79 | width: 60,
80 | cell(r) {
81 | return r + 1;
82 | },
83 | }
84 |
85 | // column header
86 | $colHeader = {
87 | height: 25,
88 | rows: 1,
89 | merges: [],
90 | cell(r, c) {
91 | return stringAt(c);
92 | },
93 | get rowHeight() {
94 | return this.height / this.rows;
95 | },
96 | }
97 |
98 | $headerLineStyle = {
99 | width: 1,
100 | color: '#e6e6e6',
101 | };
102 |
103 | $headerCellStyle = {
104 | bgcolor: '#f4f5f8',
105 | align: 'center',
106 | valign: 'middle',
107 | color: '#585757',
108 | fontSize: 9,
109 | fontName: 'Source Sans Pro',
110 | };
111 |
112 | // a highlight cell without background filled shows as focused cell
113 | $focus = undefined;
114 |
115 | // The selection range contains multiple cells
116 | $selection = undefined;
117 |
118 | $selectionStyle = {
119 | borderWidth: 2,
120 | borderColor: '#4b89ff',
121 | bgcolor: '#4b89ff14',
122 | };
123 |
124 | // row of the start position in table
125 | $startRow = 0;
126 |
127 | // col of the start position in table
128 | $startCol = 0;
129 |
130 | // count of rows scrolled
131 | $scrollRows = 0;
132 |
133 | // count of cols scrolled
134 | $scrollCols = 0;
135 |
136 | // freezed cell
137 | $freeze = [0, 0];
138 |
139 | $freezeLineStyle = {
140 | width: 2,
141 | color: '#d8d8d8',
142 | };
143 |
144 | /**
145 | * trigger the event by clicking
146 | * @param {int} type
147 | * @param {row, col, x, y, width, height } cellRect
148 | * @param {Event} evt
149 | */
150 | $onClick = (type) => { };
151 |
152 | $onSelected = () => { };
153 |
154 | constructor(container, width, height) {
155 | this.$target = container;
156 | this.$draw = Canvas2d.create(container);
157 | this.$width = width;
158 | this.$height = height;
159 | }
160 |
161 | render() {
162 | this.viewport.render(this.$draw);
163 | return this;
164 | }
165 |
166 | get viewport() {
167 | return new Viewport(this);
168 | }
169 |
170 | // ref: 'A1:B2' | 'A:B' | '1:4' | 'A1'
171 | selection(ref) {
172 | if (typeof ref === 'string') {
173 | this.$selection = newRange(ref);
174 | } else {
175 | this.$selection = ref;
176 | }
177 | this.$focus = this.$selection.start;
178 | return this;
179 | }
180 |
181 | // ref: 'A1:B2' | 'A:B' | '1:4' | 'A1'
182 | freeze(ref) {
183 | if (ref !== 'A1') {
184 | this.$startRow = this.$scrollRows;
185 | this.$startCol = this.$scrollCols;
186 | this.$scrollRows = 0;
187 | this.$scrollCols = 0;
188 | } else {
189 | this.$scrollRows = this.$startRow;
190 | this.$scrollCols = this.$startCol;
191 | this.$startRow = 0;
192 | this.$startCol = 0;
193 | }
194 | this.$freeze = expr2xy(ref).reverse();
195 | return this;
196 | }
197 |
198 | static create(cssSelector, width, height) {
199 | return new Table(cssSelector, width, height);
200 | }
201 | }
202 |
203 | // single property
204 | [
205 | 'width', 'height', 'rows', 'cols', 'row', 'col', 'cell',
206 | 'startRow', 'startCol', 'scrollRows', 'scrollCols',
207 | 'merges',
208 | 'onClick',
209 | ].forEach((it) => {
210 | Table.prototype[it] = function (arg) {
211 | this[`$${it}`] = arg;
212 | return this;
213 | };
214 | });
215 |
216 | // object property
217 | [
218 | 'lineStyle', 'cellStyle',
219 | 'headerCellStyle', 'headerLineStyle',
220 | 'selectionStyle', 'freezeLineStyle',
221 | 'rowHeader', 'colHeader',
222 | ].forEach((it) => {
223 | Table.prototype[it] = function (arg) {
224 | Object.assign(this[`$${it}`], arg || {});
225 | return this;
226 | };
227 | });
228 |
229 | export default Table;
230 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ]; do
30 | ls=$(ls -ld "$PRG")
31 | link=$(expr "$ls" : '.*-> \(.*\)$')
32 | if expr "$link" : '/.*' >/dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=$(dirname "$PRG")"/$link"
36 | fi
37 | done
38 | SAVED="$(pwd)"
39 | cd "$(dirname \"$PRG\")/" >/dev/null
40 | APP_HOME="$(pwd -P)"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=$(basename "$0")
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn() {
53 | echo "$*"
54 | }
55 |
56 | die() {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "$(uname)" in
69 | CYGWIN*)
70 | cygwin=true
71 | ;;
72 | Darwin*)
73 | darwin=true
74 | ;;
75 | MINGW*)
76 | msys=true
77 | ;;
78 | NONSTOP*)
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ]; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ]; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then
109 | MAX_FD_LIMIT=$(ulimit -H -n)
110 | if [ $? -eq 0 ]; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ]; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ]; then
130 | APP_HOME=$(cygpath --path --mixed "$APP_HOME")
131 | CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
132 | JAVACMD=$(cygpath --unix "$JAVACMD")
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
136 | SEP=""
137 | for dir in $ROOTDIRSRAW; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ]; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@"; do
149 | CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -)
150 | CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition
153 | eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
154 | else
155 | eval $(echo args$i)="\"$arg\""
156 | fi
157 | i=$(expr $i + 1)
158 | done
159 | case $i in
160 | 0) set -- ;;
161 | 1) set -- "$args0" ;;
162 | 2) set -- "$args0" "$args1" ;;
163 | 3) set -- "$args0" "$args1" "$args2" ;;
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save() {
175 | for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | exec "$JAVACMD" "$@"
184 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/table/render.js:
--------------------------------------------------------------------------------
1 | import { cellRender } from './cell-render';
2 | import { eachRanges } from './range';
3 | import { newArea } from './area';
4 |
5 | /**
6 | * render the grid lines
7 | * @param {Canvas2d} draw
8 | * @param {Area} area
9 | * @param {width, color} param2 the line style
10 | */
11 | function renderLines(draw, area, { width, color }) {
12 | // render row-col-lines
13 | if (width > 0) {
14 | draw.save().beginPath()
15 | .attr({ lineWidth: width, strokeStyle: color });
16 |
17 | area.eachRow((ri, v) => {
18 | const h = v.y + v.height;
19 | draw.line([0, h], [area.width, h]);
20 | });
21 |
22 | area.eachCol((ci, v) => {
23 | const w = v.x + v.width;
24 | draw.line([w, 0], [w, area.height]);
25 | });
26 | draw.restore();
27 | }
28 | }
29 |
30 | /**
31 | * render cell given params
32 | * @param {Canvas2d} draw
33 | * @param {int} ri the row index
34 | * @param {int} ci the col index
35 | * @param {Function} cell { text, style, type ...}
36 | * @param {x, y, width, height} cellRect
37 | * @param {style} cellStyle the style of default-cell
38 | */
39 | function renderCell(draw, ri, ci, cell, cellRect, cellStyle) {
40 | const c = cell(ri, ci);
41 | let text = '';
42 | let style = cellStyle;
43 | if (c !== undefined) {
44 | if (typeof c === 'string' || typeof c === 'number') text = `${c}`;
45 | else {
46 | text = c.text || '';
47 | if (c.style) {
48 | style = { ...style, ...c.style };
49 | }
50 | }
51 | }
52 | cellRender(draw, text, cellRect, style);
53 | }
54 |
55 | /**
56 | * render cells
57 | * @param {Canvas2d} draw
58 | * @param {string} type 'row-header' | 'col-header' | 'body'
59 | * @param {Area} area
60 | * @param {Function} cell
61 | * @param {style} cellStyle
62 | * @param {Range} selection
63 | * @param {style} selectionStyle
64 | * @param {Array} merges
65 | */
66 | function renderCells(draw, type, area, cell, cellStyle, selection, selectionStyle, merges) {
67 | draw.save().rect(0, 0, area.width, area.height).clip();
68 | area.each((ri, ci, rect) => {
69 | renderCell(draw, ri, ci, cell, rect, cellStyle);
70 | });
71 |
72 | // render merges
73 | eachRanges(merges, (it) => {
74 | if (it.intersects(area)) {
75 | renderCell(draw, it.startRow, it.startCol,
76 | cell, area.rect(it), cellStyle);
77 | }
78 | });
79 |
80 | // render selection
81 | if (selection && area.intersects(selection)) {
82 | const {
83 | x, y, width, height,
84 | } = area.rect(selection);
85 | const { bgcolor, borderWidth, borderColor } = selectionStyle;
86 | const bw = type === 'body' ? borderWidth : 0;
87 | draw.save()
88 | .attr({ fillStyle: bgcolor })
89 | .rect(x + bw / 2, y + bw / 2, width - bw, height - bw)
90 | .fill();
91 | if (type === 'body') {
92 | draw.attr({
93 | strokeStyle: borderColor,
94 | lineWidth: borderWidth,
95 | }).stroke();
96 | }
97 | draw.restore();
98 | }
99 | draw.restore();
100 | }
101 |
102 | function renderLinesAndCells(draw, type, area,
103 | cell, cellStyle, lineStyle, selection, selectionStyle, merges) {
104 | renderLines(draw, area, lineStyle);
105 | renderCells(draw, type, area, cell, cellStyle,
106 | selection, selectionStyle, merges);
107 | }
108 |
109 | // private methods --- start ----
110 |
111 | function renderRowHeader(draw, area) {
112 | const { cell, width } = this.$rowHeader;
113 | // render row-index
114 | if (width > 0) {
115 | draw.save().translate(0, area.y);
116 | const { $selection } = this;
117 | let nselection = null;
118 | if ($selection) {
119 | nselection = this.$selection.clone();
120 | nselection.startCol = 0;
121 | nselection.endCol = 0;
122 | }
123 | renderLinesAndCells(draw, 'row-header', area,
124 | cell, this.$headerCellStyle, this.$headerLineStyle,
125 | nselection, this.$selectionStyle);
126 | draw.restore();
127 | }
128 | }
129 |
130 | function renderColHeader(draw, area) {
131 | const { cell, height, merges } = this.$colHeader;
132 | // render col-index
133 | if (height > 0) {
134 | draw.save().translate(area.x, 0);
135 | const { $selection } = this;
136 | let nselection = null;
137 | if ($selection) {
138 | nselection = this.$selection.clone();
139 | nselection.startRow = 0;
140 | nselection.endRow = area.endRow;
141 | }
142 | renderLinesAndCells(draw, 'col-header', area,
143 | cell, this.$headerCellStyle, this.$headerLineStyle,
144 | nselection, this.$selectionStyle, merges);
145 | draw.restore();
146 | }
147 | }
148 |
149 | function renderBody(draw, area) {
150 | draw.save().translate(area.x, area.y);
151 | renderLinesAndCells(draw, 'body', area,
152 | this.$cell, this.$cellStyle, this.$lineStyle,
153 | this.$selection, this.$selectionStyle, this.$merges);
154 | draw.restore();
155 | }
156 |
157 | function renderFreezeLines(draw, x, y) {
158 | const [fr, fc] = this.$freeze;
159 | const { width, color } = this.$freezeLineStyle;
160 | if (width > 0 && (fr > 0 || fc > 0)) {
161 | draw.save().beginPath().attr({ lineWidth: width, strokeStyle: color });
162 | if (fr > 0) draw.line([0, y], [this.$width, y]);
163 | if (fc > 0) draw.line([x, 0], [x, this.$height]);
164 | draw.restore();
165 | }
166 | }
167 |
168 | export function render(draw,
169 | [area1, area2, area3, area4],
170 | [iarea1, iarea21, iarea23, iarea3]) {
171 | draw.resize(this.$width, this.$height);
172 |
173 | // render area-4
174 | renderBody.call(this, draw, area4);
175 |
176 | // render area-1
177 | renderBody.call(this, draw, area1);
178 | renderColHeader.call(this, draw, iarea1);
179 |
180 | // render area-3
181 | renderBody.call(this, draw, area3);
182 | renderRowHeader.call(this, draw, iarea3);
183 |
184 | // render area-2
185 | renderBody.call(this, draw, area2);
186 | renderColHeader.call(this, draw, iarea21);
187 | renderRowHeader.call(this, draw, iarea23);
188 |
189 | // render freeze
190 | renderFreezeLines.call(this, draw, area4.x, area4.y);
191 |
192 | // left-top
193 | const { x, y } = area2;
194 | if (x > 0 && y > 0) {
195 | renderLinesAndCells(draw, 'header',
196 | newArea(0, 0, 0, 0, () => ({ width: x }), () => ({ height: y })),
197 | () => '', this.$headerCellStyle, this.$headerLineStyle);
198 | }
199 | }
200 |
201 | export default {};
202 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/table/viewport.js:
--------------------------------------------------------------------------------
1 | import { endCell, eachRange, newArea } from './area';
2 | import Range, { findRanges } from './range';
3 | import { render } from './render';
4 |
5 | function $newArea(startRow, startCol, endRow, endCol, x, y) {
6 | return newArea(startRow, startCol, endRow, endCol,
7 | this.$col, this.$row, x, y);
8 | }
9 |
10 | // |
11 | // 2(top-left) | 1(top-right)
12 | // ------------------------------------
13 | // 3(bottom-left) | 4(bottom-right)
14 | // |
15 | function $newBodyAreas() {
16 | const {
17 | $startRow, $startCol, $width, $height,
18 | $colHeader, $rowHeader, $scrollRows, $scrollCols,
19 | $freeze, $row, $col, $rows, $cols,
20 | } = this;
21 | const tx = $rowHeader.width;
22 | const ty = $colHeader.height;
23 | const [fr, fc] = $freeze;
24 | const area2 = $newArea.call(this, $startRow, $startCol, fr - 1, fc - 1, tx, ty);
25 | const startRow4 = fr + $scrollRows;
26 | const startCol4 = fc + $scrollCols;
27 | const { row, col } = endCell($row, $col,
28 | startRow4, startCol4, $rows, $cols,
29 | area2.width, area2.height, $width, $height);
30 | const area4 = $newArea.call(this, startRow4, startCol4, row, col,
31 | tx + area2.width, ty + area2.height);
32 | const area1 = $newArea.call(this, $startRow, startCol4, fr - 1, col,
33 | tx + area2.width, ty + 0);
34 | const area3 = $newArea.call(this, startRow4, $startCol, row, fc - 1,
35 | tx + 0, ty + area2.height);
36 | return [area1, area2, area3, area4];
37 | }
38 |
39 | // return [1, 2-1, 2-3, 3]
40 | function $newHeaderAreas([area1, area2, area3, area4]) {
41 | const {
42 | $colHeader, $rowHeader, $row, $col,
43 | } = this;
44 | const columnHeaderRows = $colHeader.rows - 1;
45 | return [
46 | // 1
47 | newArea(0, area1.startCol, columnHeaderRows, area1.endCol,
48 | $col, () => ({ height: $colHeader.rowHeight }), area4.x, 0),
49 | // 2-1
50 | newArea(0, area2.startCol, columnHeaderRows, area2.endCol,
51 | $col, () => ({ height: $colHeader.rowHeight }), area2.x, 0),
52 | // 2-3
53 | newArea(area2.startRow, 0, area2.endRow, 0,
54 | () => ({ width: $rowHeader.width }), $row, 0, area2.y),
55 | // 3
56 | newArea(area3.startRow, 0, area3.endRow, 0,
57 | () => ({ width: $rowHeader.width }), $row, 0, area4.y),
58 | ];
59 | }
60 |
61 | function rangeInAreas([area1, area2, area3, area4],
62 | [iarea1, iarea21, iarea23, iarea3], x, y) {
63 | const { $merges, $rows, $cols } = this;
64 | const inIndexRows = x < area2.x;
65 | const inIndexCols = y < area2.y;
66 | const range = new Range(0, 0, $rows - 1, $cols - 1);
67 | const cellfn = (a) => a.cell(x, y);
68 | if (inIndexRows && inIndexCols) {
69 | return range;
70 | }
71 |
72 |
73 | if (inIndexRows) {
74 | const r = cellfn(iarea23.iny(y) ? iarea23 : iarea3).row;
75 | range.startRow = r;
76 | range.endRow = r;
77 | return range;
78 | }
79 |
80 | if (inIndexCols) {
81 | const c = cellfn(iarea21.inx(x) ? iarea21 : iarea1).col;
82 | range.startCol = c;
83 | range.endCol = c;
84 | return range;
85 | }
86 |
87 | const ary = [area4, area2, area1, area3];
88 | for (let i = 0; i < ary.length; i += 1) {
89 | const area = ary[i];
90 | if (area.inxy(x, y)) {
91 | const { row, col } = cellfn(area);
92 | const cr = findRanges($merges, (it) => it.contains(row, col));
93 | if (cr) return cr;
94 | return new Range(row, col, row, col);
95 | }
96 | }
97 | return null;
98 | }
99 |
100 | // 2 | 1
101 | // -------
102 | // 3 | 4
103 | // return [type, {row, col, x, y, width, height }, evt]
104 | function cellInAreas([area1, area2, area3, area4],
105 | [iarea1, iarea21, iarea23, iarea3], x, y) {
106 | const { $merges, $row, $col } = this;
107 | const inIndexRows = x < area2.x;
108 | const inIndexCols = y < area2.y;
109 | // const { $indexColWidth } = this;
110 | if (inIndexRows && inIndexCols) {
111 | return [2, {
112 | row: 0, col: 0, x: 0, y: 0, width: area2.x, height: area2.y,
113 | }];
114 | }
115 |
116 | const cellfn = (a) => {
117 | let ret = a.cell(x, y);
118 | const cr = findRanges($merges, (it) => it.contains(ret.row, ret.col));
119 | if (cr) {
120 | const {
121 | startRow, startCol, endRow, endCol,
122 | } = cr;
123 | const gap = { width: 0, height: 0 };
124 | eachRange(area2.endRow + 1, area4.startRow - 1, $row, (i, { height }) => {
125 | if (i <= endRow) gap.height += height;
126 | });
127 | eachRange(area2.endCol + 1, area4.startCol - 1, $col, (i, { width }) => {
128 | if (i <= endCol) gap.width += width;
129 | });
130 | if (area2.contains(startRow, startCol)) {
131 | ret = area2.rect(cr, true);
132 | ret.width -= gap.width;
133 | ret.height -= gap.height;
134 | } else if (area1.contains(startRow, startCol)) {
135 | ret = area1.rect(cr, true);
136 | ret.height -= gap.height;
137 | } else if (area3.contains(startRow, startCol)) {
138 | ret = area3.rect(cr, true);
139 | ret.width -= gap.width;
140 | } else {
141 | ret = area4.rect(cr, true);
142 | }
143 | return {
144 | row: startRow,
145 | col: startCol,
146 | ...ret,
147 | };
148 | }
149 | return ret;
150 | };
151 |
152 | if (inIndexRows) {
153 | if (iarea23.iny(y)) {
154 | return [3, cellfn(iarea23)];
155 | }
156 | return [3, cellfn(iarea3)];
157 | }
158 | if (inIndexCols) {
159 | if (iarea21.inx(x)) {
160 | return [1, cellfn(iarea21)];
161 | }
162 | return [1, cellfn(iarea1)];
163 | }
164 | const ary = [area4, area2, area1, area3];
165 | for (let i = 0; i < ary.length; i += 1) {
166 | const area = ary[i];
167 | if (area.inxy(x, y)) {
168 | return [4, cellfn(area)];
169 | }
170 | }
171 | return null;
172 | }
173 |
174 | /**
175 | * it contains header(areas), body(areas) with table
176 | */
177 | export default class Viewport {
178 | constructor(table) {
179 | this.table = table;
180 | this.body = $newBodyAreas.call(table);
181 | this.header = $newHeaderAreas.call(table, this.body);
182 | }
183 |
184 | cell(x, y) {
185 | return cellInAreas.call(this.table, this.body, this.header, x, y);
186 | }
187 |
188 | range(x, y) {
189 | return rangeInAreas.call(this.table, this.body, this.header, x, y);
190 | }
191 |
192 | render(draw) {
193 | render.call(this.table, draw, this.body, this.header);
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/entry/src/main/js/default/components/table/area.js:
--------------------------------------------------------------------------------
1 | import Range from './range';
2 |
3 | /**
4 | * each range of row or col
5 | * @param {int} min the min value
6 | * @param {int} max the max value
7 | * @param {Function} getv (index) => v
8 | * @param {Function} cb (index, v) => {}
9 | */
10 | export function eachRange(min, max, getv, cb) {
11 | for (let i = min; i <= max; i += 1) {
12 | const v = getv(i);
13 | if (v.hide !== true) cb(i, v);
14 | }
15 | }
16 |
17 | /**
18 | * get the end row given params...
19 | * @param {Function} row (index) => { height, hide }
20 | * @param {int} minRow the min row
21 | * @param {int} maxRow the max row
22 | * @param {int} miny the min value on y-axis
23 | * @param {int} maxy the max value on y-axis
24 | */
25 | function endCellRow(row, minRow, maxRow, miny, maxy) {
26 | let r = minRow;
27 | let y = miny;
28 | let lasth = 0;
29 | while (y < maxy && r <= maxRow) {
30 | const { height, hide } = row(r);
31 | if (hide !== true) {
32 | lasth = height;
33 | y += height;
34 | }
35 | r += 1;
36 | }
37 | y -= lasth;
38 | return { row: r - 1, y, height: lasth };
39 | }
40 |
41 | /**
42 | * get the end col given params...
43 | * @param {Function} col (index) => { width, hide }
44 | * @param {int} minCol the min col
45 | * @param {int} maxCol the max col
46 | * @param {int} minx the min value on x-axis
47 | * @param {int} maxx the max value on x-axis
48 | */
49 | function endCellCol(col, minCol, maxCol, minx, maxx) {
50 | let c = minCol;
51 | let x = minx;
52 | let lastw = 0;
53 | while (x < maxx && c <= maxCol) {
54 | const { width, hide } = col(c);
55 | if (hide !== true) {
56 | lastw = width;
57 | x += width;
58 | }
59 | c += 1;
60 | }
61 | x -= lastw;
62 | return { col: c - 1, x, width: lastw };
63 | }
64 |
65 | export function endCell(row, col,
66 | minRow, minCol, maxRow, maxCol,
67 | minx, miny, maxx, maxy) {
68 | return {
69 | ...endCellRow(row, minRow, maxRow, miny, maxy),
70 | ...endCellCol(col, minCol, maxCol, minx, maxx),
71 | };
72 | }
73 |
74 | /**
75 | * it's a range with { x, y, width, height }
76 | * and calculating height of row and width of col..
77 | */
78 | export default class Area extends Range {
79 | constructor(startRow, startCol, endRow, endCol, col, row, x = 0, y = 0) {
80 | super(startRow, startCol, endRow, endCol);
81 | // the function of returning { width, hide }
82 | this.$col = col;
83 | // the function of returning { height, hide }
84 | this.$row = row;
85 |
86 | // cache with row and col
87 | // { rowIndex: { height, hide }}
88 | this.$rowMap = new Map();
89 | // { colIndex: { width, hide }}
90 | this.$colMap = new Map();
91 |
92 | this.x = x;
93 | this.y = y;
94 | this.width = 0;
95 | this.height = 0;
96 |
97 | eachRange(startRow, endRow, (i) => row(i), (i, { height }) => {
98 | this.$rowMap.set(i, { y: this.height, height });
99 | this.height += height;
100 | });
101 | eachRange(startCol, endCol, (i) => col(i), (i, { width }) => {
102 | this.$colMap.set(i, { x: this.width, width });
103 | this.width += width;
104 | });
105 | }
106 |
107 | /**
108 | * get {y, height} given index, endIndex
109 | * y: the offset on y-axis
110 | * @param {int} index
111 | * @param {int} endIndex (option param)
112 | * @returns {y, height}
113 | * @example
114 | * row(5)
115 | * row(5, 10)
116 | */
117 | row(index, endIndex) {
118 | const { $rowMap, startRow, $row } = this;
119 | if ((endIndex === undefined || index === endIndex) && $rowMap.has(index)) {
120 | return $rowMap.get(index);
121 | }
122 | if (index < startRow) {
123 | let y = 0;
124 | let height = 0;
125 | eachRange(index, endIndex, (i) => $row(i), (i, v) => {
126 | if (i < startRow) y -= v.height;
127 | height += v.height;
128 | });
129 | return { y, height };
130 | }
131 | const { y } = $rowMap.get(index);
132 | let height = 0;
133 | eachRange(index, endIndex, (i) => $row(i), (i, v) => {
134 | height += v.height;
135 | });
136 | return { y, height };
137 | }
138 |
139 | /**
140 | * get {x, width} given index, endIndex
141 | * x: the offset on y-axis
142 | * @param {int} index
143 | * @param {int} endIndex (option param)
144 | * @returns {x, width}
145 | */
146 | col(index, endIndex) {
147 | const { $colMap, startCol, $col } = this;
148 | if ((endIndex === undefined || index === endIndex) && $colMap.has(index)) {
149 | return $colMap.get(index);
150 | }
151 | if (index < startCol) {
152 | let x = 0;
153 | let width = 0;
154 | eachRange(index, endIndex, (i) => $col(i), (i, v) => {
155 | if (i < startCol) x -= v.width;
156 | width += v.width;
157 | });
158 | return { x, width };
159 | }
160 | const { x } = $colMap.get(index);
161 | let width = 0;
162 | eachRange(index, endIndex, (i) => $col(i), (i, v) => {
163 | width += v.width;
164 | });
165 | return { x, width };
166 | }
167 |
168 | /**
169 | * check whether or not x contained in area
170 | * @param {int} x offset on x-axis
171 | */
172 | inx(x) {
173 | return x >= this.x && x < (this.x + this.width);
174 | }
175 |
176 | /**
177 | * check whether or not y contained in area
178 | * @param {int} y offset on y-axis
179 | */
180 | iny(y) {
181 | return y >= this.y && y < (this.y + this.height);
182 | }
183 |
184 | /**
185 | * check whether or not the tabe contains (x, y)
186 | * @param {int} x offset on x-axis
187 | * @param {int} y offset on y-axis
188 | */
189 | inxy(x, y) {
190 | return this.inx(x) && this.iny(y);
191 | }
192 |
193 | /**
194 | * get cell given x, y in canvas!!!!
195 | * @param {int} x offset on x-axis
196 | * @param {int} y offset on y-axis
197 | * @returns { row, col, x, y, width, height }
198 | */
199 | cell(x, y) {
200 | return endCell(this.$row, this.$col,
201 | this.startRow, this.startCol, this.endRow, this.endCol,
202 | this.x, this.y, x, y);
203 | }
204 |
205 | /**
206 | * get rect given range in area
207 | * @param {Range} range
208 | * @param {boolean} isCanvas
209 | * @returns { x, y, width, height }
210 | */
211 | rect(range, inCanvas = false) {
212 | const c = this.col(range.startCol, range.endCol);
213 | const r = this.row(range.startRow, range.endRow);
214 | if (inCanvas) {
215 | c.x += this.x;
216 | r.y += this.y;
217 | }
218 | return {
219 | ...c, ...r,
220 | };
221 | }
222 |
223 | eachRow(cb) {
224 | eachRange(this.startRow, this.endRow,
225 | (i) => this.$rowMap.get(i), (i, v) => cb(i, v));
226 | }
227 |
228 | eachCol(cb) {
229 | eachRange(this.startCol, this.endCol,
230 | (i) => this.$colMap.get(i), (i, v) => cb(i, v));
231 | }
232 |
233 | each(cb) {
234 | this.eachRow((ri, { y, height }) => {
235 | this.eachCol((ci, { x, width }) => {
236 | cb(ri, ci, {
237 | x, y, width, height,
238 | });
239 | });
240 | });
241 | }
242 | }
243 |
244 | export function newArea(...args) {
245 | return new Area(...args);
246 | }
247 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OpenHarmonySheet
2 |
3 | 基于 `Canvas` 实现的高性能 `Excel` 表格引擎组件 [OpenHarmonySheet](https://github.com/Wscats/sheet)。
4 |
5 | 由于大部分前端项目渲染层是使用框架根据排版模型树结构逐层渲染的,整棵渲染树也是与排版模型树一一对应。因此,整个渲染的节点也非常多。项目较大时,性能会受到较大的影响。
6 |
7 | 为了提升渲染性能,提供更优质的编辑体验从 `DOM` 更换成 `Canvas` 渲染,方便开发者构建重前端大型在线文档项目,**在国内外实现类似引擎的公司仅仅只有几家**,如:腾讯文档,金山文档和谷歌文档等。
8 |
9 |
10 |
11 | 在项目中引入 `` 组件即可,使用方法如下:
12 |
13 | ```html
14 |
15 |
16 |
25 | ```
26 |
27 | # 生命周期和事件
28 |
29 | - sheet 表格数据
30 | - @sheet-show 表格显示
31 | - @sheet-hide 表格隐藏
32 | - @click-cell-start 单元格点击前
33 | - @click-cell-end 单元格点击后
34 | - @click-cell-longpress 长按表格
35 | - @change 修改单元格数据
36 |
37 | 比如,我们在示例中可以监听 `长按` 事件,当用户 `长按` 的时候弹出 `对话框`,示例代码如下:
38 |
39 | ```ts
40 | clickCellLongpress(evt) {
41 | prompt.showDialog({
42 | buttons: [{
43 | text: '测试',
44 | color: '#666666',
45 | }],
46 | });
47 | }
48 | ```
49 |
50 | 以上所有的接口都会返回一个详细的 `sheet` 对象,里面含有以下信息:
51 |
52 | - el 表格的节点
53 | - textarea 单元格输入框节点
54 | - viewport 单元格高亮选框
55 | - table 单元格操作对象
56 |
57 | ```ts
58 | sheetShow(sheet) {
59 | this.el = sheet.detail.el;
60 | this.textarea = sheet.detail.textarea;
61 | this.viewport = sheet.detail.viewport;
62 | this.table = sheet.detail.table;
63 | }
64 | ```
65 |
66 | # API 接口
67 |
68 | 渲染引擎封装好了常用的表格数据操作等接口。
69 |
70 | - `this.table.xxx`
71 |
72 | 用于帮助你操作单元格的所有数据和格式,也极大方便你自定义一个功能完整的工具栏:
73 |
74 |
75 |
76 | - `this.viewport.xxx`
77 |
78 | 用于帮助你操作单元格上层的高亮选框。
79 |
80 | - `this.textarea.xxx`
81 |
82 | `this.textarea` 是对鸿蒙的原生 `